diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4ce6fdd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,340 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
\ No newline at end of file
diff --git a/ArcWelder.sln b/ArcWelder.sln
new file mode 100644
index 0000000..9bfbb73
--- /dev/null
+++ b/ArcWelder.sln
@@ -0,0 +1,81 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30011.22
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArcWelder", "ArcWelder\ArcWelder.vcxproj", "{1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodeProcessorLib", "GcodeProcessorLib\GcodeProcessorLib.vcxproj", "{31478BAE-104B-4CC3-9876-42FA90CBD5FE}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PyArcWelder", "PyArcWelder\PyArcWelder.vcxproj", "{DB476DBA-77D5-40A7-ADAB-D9901F32B270}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArcWelderConsole", "ArcWelderConsole\ArcWelderConsole.vcxproj", "{F4910B67-FE16-40EA-9BD5-91017C569B0D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArcWelderInverseProcessor", "ArcWelderInverseProcessor\ArcWelderInverseProcessor.vcxproj", "{9C40BB30-5186-4181-94D6-AC8DFE361A5A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArcWelderTest", "ArcWelderTest\ArcWelderTest.vcxproj", "{18D7E538-6ACE-44E4-B83E-31C3E44D4227}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Debug|x64.ActiveCfg = Debug|x64
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Debug|x64.Build.0 = Debug|x64
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Debug|x86.ActiveCfg = Debug|Win32
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Debug|x86.Build.0 = Debug|Win32
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x64.ActiveCfg = Release|x64
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x64.Build.0 = Release|x64
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x86.ActiveCfg = Release|Win32
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x86.Build.0 = Release|Win32
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Debug|x64.ActiveCfg = Debug|x64
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Debug|x64.Build.0 = Debug|x64
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Debug|x86.ActiveCfg = Debug|Win32
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Debug|x86.Build.0 = Debug|Win32
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Release|x64.ActiveCfg = Release|x64
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Release|x64.Build.0 = Release|x64
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Release|x86.ActiveCfg = Release|Win32
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}.Release|x86.Build.0 = Release|Win32
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Debug|x64.ActiveCfg = Debug|x64
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Debug|x64.Build.0 = Debug|x64
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Debug|x86.ActiveCfg = Debug|Win32
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Debug|x86.Build.0 = Debug|Win32
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Release|x64.ActiveCfg = Release|x64
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Release|x64.Build.0 = Release|x64
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Release|x86.ActiveCfg = Release|Win32
+ {DB476DBA-77D5-40A7-ADAB-D9901F32B270}.Release|x86.Build.0 = Release|Win32
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Debug|x64.ActiveCfg = Debug|x64
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Debug|x64.Build.0 = Debug|x64
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Debug|x86.ActiveCfg = Debug|Win32
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Debug|x86.Build.0 = Debug|Win32
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Release|x64.ActiveCfg = Release|x64
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Release|x64.Build.0 = Release|x64
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Release|x86.ActiveCfg = Release|Win32
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}.Release|x86.Build.0 = Release|Win32
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Debug|x64.ActiveCfg = Debug|x64
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Debug|x64.Build.0 = Debug|x64
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Debug|x86.ActiveCfg = Debug|Win32
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Debug|x86.Build.0 = Debug|Win32
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Release|x64.ActiveCfg = Release|x64
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Release|x64.Build.0 = Release|x64
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Release|x86.ActiveCfg = Release|Win32
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}.Release|x86.Build.0 = Release|Win32
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Debug|x64.ActiveCfg = Debug|x64
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Debug|x64.Build.0 = Debug|x64
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Debug|x86.ActiveCfg = Debug|Win32
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Debug|x86.Build.0 = Debug|Win32
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Release|x64.ActiveCfg = Release|x64
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Release|x64.Build.0 = Release|x64
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Release|x86.ActiveCfg = Release|Win32
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {46059DE8-A2F4-453F-9F33-F12ABC8010EF}
+ EndGlobalSection
+EndGlobal
diff --git a/ArcWelder/ArcWelder.vcxproj b/ArcWelder/ArcWelder.vcxproj
new file mode 100644
index 0000000..428e87b
--- /dev/null
+++ b/ArcWelder/ArcWelder.vcxproj
@@ -0,0 +1,158 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}
+ ArcWelder
+ 10.0
+
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ArcWelder/ArcWelder.vcxproj.filters b/ArcWelder/ArcWelder.vcxproj.filters
new file mode 100644
index 0000000..e79f8a0
--- /dev/null
+++ b/ArcWelder/ArcWelder.vcxproj.filters
@@ -0,0 +1,42 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp
new file mode 100644
index 0000000..47a61f2
--- /dev/null
+++ b/ArcWelder/arc_welder.cpp
@@ -0,0 +1,745 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "arc_welder.h"
+#include
+#include
+#include
+#include "utilities.h"
+#include
+#include
+#include
+#include
+arc_welder::arc_welder(std::string source_path, std::string target_path, logger * log, double resolution_mm, gcode_position_args args) : current_arc_(gcode_position_args_.position_buffer_size - 5, resolution_mm)
+{
+ p_logger_ = log;
+ debug_logging_enabled_ = false;
+ info_logging_enabled_ = false;
+ error_logging_enabled_ = false;
+ verbose_logging_enabled_ = false;
+
+ logger_type_ = 0;
+ progress_callback_ = NULL;
+ verbose_output_ = false;
+ absolute_e_offset_total_ = 0;
+ source_path_ = source_path;
+ target_path_ = target_path;
+ resolution_mm_ = resolution_mm;
+ gcode_position_args_ = args;
+ notification_period_seconds = 1;
+ lines_processed_ = 0;
+ gcodes_processed_ = 0;
+ file_size_ = 0;
+ last_gcode_line_written_ = 0;
+ points_compressed_ = 0;
+ arcs_created_ = 0;
+ waiting_for_line_ = false;
+ waiting_for_arc_ = false;
+ absolute_e_offset_ = 0;
+ gcode_position_args_.set_num_extruders(8);
+ for (int index = 0; index < 8; index++)
+ {
+ gcode_position_args_.retraction_lengths[0] = .0001;
+ gcode_position_args_.z_lift_heights[0] = 0.001;
+ gcode_position_args_.x_firmware_offsets[0] = 0.0;
+ gcode_position_args_.y_firmware_offsets[0] = 0.0;
+ }
+
+ // We don't care about the printer settings, except for g91 influences extruder.
+ p_source_position_ = new gcode_position(gcode_position_args_);
+
+ // Create a list of commands that will need rewritten absolute e values
+ std::vector absolute_e_rewrite_command_names;
+ absolute_e_rewrite_command_names.push_back("G0");
+ absolute_e_rewrite_command_names.push_back("G1");
+ absolute_e_rewrite_command_names.push_back("G2");
+ absolute_e_rewrite_command_names.push_back("G3");
+ //absolute_e_rewrite_command_names.push_back("G92");
+
+ for (unsigned int index = 0; index < absolute_e_rewrite_command_names.size(); index++)
+ {
+ absolute_e_rewrite_commands_.insert(absolute_e_rewrite_command_names[index]);
+ }
+}
+
+arc_welder::arc_welder(std::string source_path, std::string target_path, logger* log, double resolution_mm, bool g90_g91_influences_extruder, int buffer_size)
+ : arc_welder(source_path, target_path, log, resolution_mm, arc_welder::get_args_(g90_g91_influences_extruder, buffer_size))
+{
+
+}
+
+arc_welder::arc_welder(std::string source_path, std::string target_path, logger * log, double resolution_mm, bool g90_g91_influences_extruder, int buffer_size, progress_callback callback)
+ : arc_welder(source_path, target_path, log, resolution_mm, arc_welder::get_args_(g90_g91_influences_extruder, buffer_size))
+{
+ progress_callback_ = callback;
+}
+
+gcode_position_args arc_welder::get_args_(bool g90_g91_influences_extruder, int buffer_size)
+{
+ gcode_position_args args;
+ // Configure gcode_position_args
+ args.g90_influences_extruder = g90_g91_influences_extruder;
+ args.position_buffer_size = buffer_size;
+ args.autodetect_position = true;
+ args.home_x = 0;
+ args.home_x_none = true;
+ args.home_y = 0;
+ args.home_y_none = true;
+ args.home_z = 0;
+ args.home_z_none = true;
+ args.shared_extruder = true;
+ args.zero_based_extruder = true;
+
+
+ args.default_extruder = 0;
+ args.xyz_axis_default_mode = "absolute";
+ args.e_axis_default_mode = "absolute";
+ args.units_default = "millimeters";
+ args.location_detection_commands = std::vector();
+ args.is_bound_ = false;
+ args.is_circular_bed = false;
+ args.x_min = -9999;
+ args.x_max = 9999;
+ args.y_min = -9999;
+ args.y_max = 9999;
+ args.z_min = -9999;
+ args.z_max = 9999;
+ return args;
+}
+
+arc_welder::~arc_welder()
+{
+ delete p_source_position_;
+}
+
+void arc_welder::set_logger_type(int logger_type)
+{
+ logger_type_ = logger_type;
+}
+
+void arc_welder::reset()
+{
+ lines_processed_ = 0;
+ gcodes_processed_ = 0;
+ last_gcode_line_written_ = 0;
+ file_size_ = 0;
+ points_compressed_ = 0;
+ arcs_created_ = 0;
+ waiting_for_line_ = false;
+ waiting_for_arc_ = false;
+ absolute_e_offset_ = 0;
+}
+
+long arc_welder::get_file_size(const std::string& file_path)
+{
+ // Todo: Fix this function. This is a pretty weak implementation :(
+ std::ifstream file(file_path.c_str(), std::ios::in | std::ios::binary);
+ const long l = (long)file.tellg();
+ file.seekg(0, std::ios::end);
+ const long m = (long)file.tellg();
+ file.close();
+ return (m - l);
+}
+
+double arc_welder::get_next_update_time() const
+{
+ return clock() + (notification_period_seconds * CLOCKS_PER_SEC);
+}
+
+double arc_welder::get_time_elapsed(double start_clock, double end_clock)
+{
+ return static_cast(end_clock - start_clock) / CLOCKS_PER_SEC;
+}
+
+void arc_welder::process()
+{
+ verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, VERBOSE);
+ debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, DEBUG);
+ info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, INFO);
+ error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, ERROR);
+ // reset tracking variables
+ reset();
+ // local variable to hold the progress update return. If it's false, we will exit.
+ bool continue_processing = true;
+
+ // Create a stringstream we can use for messaging.
+ std::stringstream stream;
+
+ int read_lines_before_clock_check = 5000;
+ double next_update_time = get_next_update_time();
+ const clock_t start_clock = clock();
+ file_size_ = get_file_size(source_path_);
+ // Create the source file read stream and target write stream
+ std::ifstream gcodeFile;
+ gcodeFile.open(source_path_.c_str());
+ output_file_.open(target_path_.c_str());
+ std::string line;
+ int lines_with_no_commands = 0;
+ gcodeFile.sync_with_stdio(false);
+ output_file_.sync_with_stdio(false);
+ if (gcodeFile.is_open())
+ {
+ if (output_file_.is_open())
+ {
+ if (info_logging_enabled_)
+ {
+ stream.clear();
+ stream.str("");
+ stream << "Opened file for reading. File Size: " << file_size_;
+ p_logger_->log(logger_type_, DEBUG, stream.str());
+ }
+ parsed_command cmd;
+ // Communicate every second
+ while (std::getline(gcodeFile, line) && continue_processing)
+ {
+ lines_processed_++;
+
+ cmd.clear();
+ parser_.try_parse_gcode(line.c_str(), cmd);
+ bool has_gcode = false;
+ if (cmd.gcode.length() > 0)
+ {
+ has_gcode = true;
+ gcodes_processed_++;
+ }
+ else
+ {
+ lines_with_no_commands++;
+ }
+
+ // Always process the command through the printer, even if no command is found
+ // This is important so that comments can be analyzed
+ //std::cout << "stabilization::process_file - updating position...";
+ process_gcode(cmd, false);
+
+ // Only continue to process if we've found a command.
+ if (has_gcode)
+ {
+ if ((lines_processed_ % read_lines_before_clock_check) == 0 && next_update_time < clock())
+ {
+ long file_position = static_cast(gcodeFile.tellg());
+ // ToDo: tellg does not do what I think it does, but why?
+ long bytesRemaining = file_size_ - file_position;
+ double percentProgress = static_cast(file_position) / static_cast(file_size_) * 100.0;
+ double secondsElapsed = get_time_elapsed(start_clock, clock());
+ double bytesPerSecond = static_cast(file_position) / secondsElapsed;
+ double secondsToComplete = bytesRemaining / bytesPerSecond;
+ continue_processing = on_progress_(percentProgress, secondsElapsed, secondsToComplete, gcodes_processed_, lines_processed_, points_compressed_, arcs_created_);
+ next_update_time = get_next_update_time();
+ }
+ }
+ }
+
+ if (current_arc_.is_shape() && waiting_for_arc_)
+ {
+ process_gcode(cmd, true);
+ }
+ write_unwritten_gcodes_to_file();
+
+ output_file_.close();
+ }
+ else
+ {
+ p_logger_->log_exception(logger_type_, "Unable to open the output file for writing.");
+ }
+ gcodeFile.close();
+ }
+ else
+ {
+ p_logger_->log_exception(logger_type_, "Unable to open the gcode file for processing.");
+ }
+
+ const clock_t end_clock = clock();
+ const double total_seconds = static_cast(end_clock - start_clock) / CLOCKS_PER_SEC;
+ on_progress_(100, total_seconds, 0, gcodes_processed_, lines_processed_, points_compressed_, arcs_created_);
+}
+
+bool arc_welder::on_progress_(double percentComplete, double seconds_elapsed, double estimatedSecondsRemaining, int gcodesProcessed, int linesProcessed, int points_compressed, int arcs_created)
+{
+ if (progress_callback_ != NULL)
+ {
+ return progress_callback_(percentComplete, seconds_elapsed, estimatedSecondsRemaining, gcodesProcessed, linesProcessed, points_compressed, arcs_created);
+ }
+ std::stringstream stream;
+ if (debug_logging_enabled_)
+ {
+ stream << percentComplete << "% complete in " << seconds_elapsed << " seconds with " << estimatedSecondsRemaining << " seconds remaining. Gcodes Processed:" << gcodesProcessed << ", Current Line:" << linesProcessed << ", Points Compressed:" << points_compressed << ", ArcsCreated:" << arcs_created;
+ p_logger_->log(logger_type_, DEBUG, stream.str());
+ }
+
+ return true;
+}
+
+int arc_welder::process_gcode(parsed_command cmd, bool is_end)
+{
+ // Update the position for the source gcode file
+ p_source_position_->update(cmd, lines_processed_, gcodes_processed_, -1);
+
+ position* p_cur_pos = p_source_position_->get_current_position_ptr();
+ position* p_pre_pos = p_source_position_->get_previous_position_ptr();
+ //std::cout << lines_processed_ << " - " << cmd.gcode << ", CurrentEAbsolute: " << cur_extruder.e <<", ExtrusionLength: " << cur_extruder.extrusion_length << ", Retraction Length: " << cur_extruder.retraction_length << ", IsExtruding: " << cur_extruder.is_extruding << ", IsRetracting: " << cur_extruder.is_retracting << ".\n";
+
+ int lines_written = 0;
+ // see if this point is an extrusion
+
+ bool arc_added = false;
+ bool clear_shapes = false;
+
+ extruder extruder_current = p_cur_pos->get_current_extruder();
+ point p(p_cur_pos->x, p_cur_pos->y, p_cur_pos->z, extruder_current.e_relative);
+
+ // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position
+ // TODO: Handle relative XYZ axis. This is possible, but maybe not so important.
+ if (
+ !is_end && cmd.is_known_command && !cmd.is_empty && (
+ (cmd.command == "G0" || cmd.command == "G1") &&
+ utilities::is_equal(p_cur_pos->z, p_pre_pos->z) &&
+ !p_cur_pos->is_relative &&
+ (
+ !waiting_for_arc_ ||
+ (p_pre_pos->get_current_extruder().is_extruding && extruder_current.is_extruding) ||
+ (p_pre_pos->get_current_extruder().is_retracting && extruder_current.is_retracting)
+ ) &&
+ p_cur_pos->is_extruder_relative == p_pre_pos->is_extruder_relative &&
+ (!waiting_for_arc_ || p_pre_pos->f == p_cur_pos->f) &&
+ (!waiting_for_arc_ || p_pre_pos->feature_type_tag == p_cur_pos->feature_type_tag)
+ )
+ ) {
+
+ if (!waiting_for_arc_)
+ {
+ if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Starting new arc from Gcode:" + cmd.gcode);
+ }
+ write_unwritten_gcodes_to_file();
+ // add the previous point as the starting point for the current arc
+ point previous_p(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_pre_pos->get_current_extruder().e_relative);
+ // Don't add any extrusion, or you will over extrude!
+ //std::cout << "Trying to add first point (" << p.x << "," << p.y << "," << p.z << ")...";
+ current_arc_.try_add_point(previous_p, 0);
+ }
+
+ double e_relative = p_cur_pos->get_current_extruder().e_relative;
+ int num_points = current_arc_.get_num_segments();
+ arc_added = current_arc_.try_add_point(p, e_relative);
+ if (arc_added)
+ {
+ if (!waiting_for_arc_)
+ {
+ waiting_for_arc_ = true;
+ }
+ else
+ {
+ if (debug_logging_enabled_)
+ {
+ if (num_points+1 == current_arc_.get_num_segments())
+ {
+ p_logger_->log(logger_type_, DEBUG, "Adding point to arc from Gcode:" + cmd.gcode);
+ }
+ {
+ p_logger_->log(logger_type_, DEBUG, "Removed start point from arc and added a new point from Gcode:" + cmd.gcode);
+ }
+ }
+ }
+ }
+ }
+ else if (debug_logging_enabled_ ){
+ if (is_end)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists.");
+ }
+ else if (!cmd.is_empty)
+ {
+ if (!cmd.is_known_command)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode);
+ }
+ else if (cmd.command != "G0" && cmd.command != "G1")
+ {
+ p_logger_->log(logger_type_, DEBUG, "Command '"+ cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode);
+ }
+ else if (!utilities::is_equal(p_cur_pos->z, p_pre_pos->z))
+ {
+ p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode);
+ }
+ else if (p_cur_pos->is_relative)
+ {
+ p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode);
+ }
+ else if (
+ waiting_for_arc_ && !(
+ (p_pre_pos->get_current_extruder().is_extruding && extruder_current.is_extruding) ||
+ (p_pre_pos->get_current_extruder().is_retracting && extruder_current.is_retracting)
+ )
+ )
+ {
+ std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode;
+ if (verbose_logging_enabled_)
+ {
+ extruder previous_extruder = p_pre_pos->get_current_extruder();
+ message.append(
+ " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) +
+ ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) +
+ ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") +
+ ", Retraction: " + utilities::to_string(extruder_current.retraction_length) +
+ ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) +
+ ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") +
+ ", Extruding: " + (extruder_current.is_extruding ? "True" : "False")
+ );
+ message.append(
+ "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) +
+ ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) +
+ ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") +
+ ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) +
+ ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) +
+ ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") +
+ ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False")
+ );
+ p_logger_->log(logger_type_, VERBOSE, message);
+ }
+ else
+ {
+ p_logger_->log(logger_type_, DEBUG, message);
+ }
+
+ }
+ else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode);
+ }
+ else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode);
+ }
+ else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode);
+ }
+ else
+ {
+ // Todo: Add all the relevant values
+ p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode);
+ }
+ }
+ }
+ if (!arc_added)
+ {
+ if (current_arc_.get_num_segments() < current_arc_.get_min_segments()) {
+ if (debug_logging_enabled_ && !cmd.is_empty)
+ {
+ if (current_arc_.get_num_segments() != 0)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode);
+ }
+
+ }
+ waiting_for_arc_ = false;
+ current_arc_.clear();
+ }
+ else if (waiting_for_arc_)
+ {
+
+ if (current_arc_.is_shape())
+ {
+ // increment our statistics
+ points_compressed_ += current_arc_.get_num_segments()-1;
+ arcs_created_++;
+ //std::cout << "Arc shape found.\n";
+ // Get the comment now, before we remove the previous comments
+ std::string comment = get_comment_for_arc();
+ // remove the same number of unwritten gcodes as there are arc segments, minus 1 for the start point
+ // Which isn't a movement
+ // note, skip the first point, it is the starting point
+ for (int index = 0; index < current_arc_.get_num_segments() - 1; index++)
+ {
+ unwritten_commands_.pop_back();
+ }
+ // get the current absolute e coordinate of the previous position (the current position is not included in
+ // the arc) so we can make any adjustments that are necessary.
+ double current_f = p_pre_pos->f;
+
+ // IMPORTANT NOTE: p_cur_pos and p_pre_pos will NOT be usable beyond this point.
+ p_pre_pos = NULL;
+ p_cur_pos = NULL;
+ // Undo the previous updates that will be turned into the arc, including the current position
+ for (int index = 0; index < current_arc_.get_num_segments(); index++)
+ {
+ undo_commands_.push_back(p_source_position_->get_current_position_ptr()->command);
+ p_source_position_->undo_update();
+ }
+ //position * p_undo_positions = p_source_position_->undo_update(current_arc_.get_num_segments());
+
+ // Set the current feedrate if it is different, else set to 0 to indicate that no feedrate should be included
+ if(p_source_position_->get_current_position_ptr()->f == current_f)
+ {
+ current_f = 0;
+ }
+
+ // Craete the arc gcode
+ std::string gcode = get_arc_gcode(current_f, comment);
+
+ if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Arc created with " + std::to_string(current_arc_.get_num_segments()) + " segments: " + gcode);
+ }
+
+ // parse the arc gcode
+ parsed_command new_command;
+ bool parsed = parser_.try_parse_gcode(gcode.c_str(), new_command);
+ if (!parsed)
+ {
+ if (error_logging_enabled_)
+ {
+ p_logger_->log_exception(logger_type_, "Unable to parse arc command! Fatal Error.");
+ }
+ throw std::exception();
+ }
+ // update the position processor and add the command to the unwritten commands list
+ p_source_position_->update(new_command, lines_processed_, gcodes_processed_, -1);
+ unwritten_commands_.push_back(unwritten_command(p_source_position_->get_current_position_ptr()));
+
+ // write all unwritten commands (if we don't do this we'll mess up absolute e by adding an offset to the arc)
+ // including the most recent arc command BEFORE updating the absolute e offset
+ write_unwritten_gcodes_to_file();
+
+ // If the e values are not equal, use G91 to adjust the current absolute e position
+ double difference = 0;
+ double new_e_rel_relative = p_source_position_->get_current_position().get_current_extruder().e_relative;
+ double old_e_relative = current_arc_.get_shape_e_relative();
+
+ // See if any offset needs to be applied for absolute E coordinates
+ if (
+ !utilities::is_equal(new_e_rel_relative, old_e_relative))
+ {
+ // Calculate the difference between the original absolute e and
+ // change made by G2/G3
+ difference = new_e_rel_relative - old_e_relative;
+ // Adjust the absolute E offset based on the difference
+ // We need to do this AFTER writing the modified gcode(arc), since the
+ // difference is based on that.
+ absolute_e_offset_ += difference;
+ if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Adjusting absolute extrusion by " + utilities::to_string(difference) + "mm. New Offset: " + utilities::to_string(difference));
+ }
+ }
+
+
+ // Undo the arc update and re-apply the original commands to the position processor so that subsequent
+ // gcodes in the file are interpreted properly. Do NOT add the most recent command
+ // since it will be reprocessed
+ p_source_position_->undo_update();
+
+ for (int index = current_arc_.get_num_segments() - 1; index > 0; index--)
+ {
+ parsed_command cmd = undo_commands_.pop_back();
+ p_source_position_->update(undo_commands_[index], lines_processed_, gcodes_processed_, -1);
+ }
+ undo_commands_.clear();
+ // Now clear the arc and flag the processor as not waiting for an arc
+ waiting_for_arc_ = false;
+ current_arc_.clear();
+
+
+ // Reprocess this line
+ if (!is_end)
+ {
+ return process_gcode(cmd, false);
+ }
+ else
+ {
+ if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Final arc created, exiting.");
+ }
+ return 0;
+ }
+
+ }
+ else
+ {
+ if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "The current arc is not a valid arc, resetting.");
+ }
+ current_arc_.clear();
+ waiting_for_arc_ = false;
+ }
+ }
+ else if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "Could not add point to arc from gcode:" + cmd.gcode);
+ }
+
+ }
+ if (clear_shapes)
+ {
+ waiting_for_arc_ = false;
+ current_arc_.clear();
+ // The current command is unwritten, add it.
+ unwritten_commands_.push_back(unwritten_command(p_source_position_->get_current_position_ptr()));
+ }
+ else if (waiting_for_arc_ || !arc_added)
+ {
+
+ unwritten_commands_.push_back(unwritten_command(p_source_position_->get_current_position_ptr()));
+
+ }
+ if (!waiting_for_arc_)
+ {
+ write_unwritten_gcodes_to_file();
+ }
+ if (cmd.command == "G92")
+ {
+ // See if there is an E parameter
+ for (unsigned int parameter_index = 0; parameter_index < cmd.parameters.size(); parameter_index++)
+ {
+ parsed_command_parameter param = cmd.parameters[parameter_index];
+ if (param.name == "E")
+ {
+ absolute_e_offset_ = 0;
+ if (debug_logging_enabled_)
+ {
+ p_logger_->log(logger_type_, DEBUG, "G92 found that set E axis, resetting absolute offset.");
+ }
+ }
+ }
+ }
+
+ return lines_written;
+}
+
+std::string arc_welder::get_comment_for_arc()
+{
+ // build a comment string from the commands making up the arc
+ // We need to start with the first command entered.
+ int comment_index = unwritten_commands_.count() - (current_arc_.get_num_segments() - 1);
+ std::string comment;
+ for (; comment_index < unwritten_commands_.count(); comment_index++)
+ {
+ std::string old_comment = unwritten_commands_[comment_index].command.comment;
+ if (old_comment != comment && old_comment.length() > 0)
+ {
+ if (comment.length() > 0)
+ {
+ comment += " - ";
+ }
+ comment += old_comment;
+ }
+ }
+ return comment;
+}
+
+std::string arc_welder::create_g92_e(double absolute_e)
+{
+ std::stringstream stream;
+ stream << std::fixed << std::setprecision(5);
+ stream << "G92 E" << absolute_e;
+ return stream.str();
+}
+
+int arc_welder::write_gcode_to_file(std::string gcode)
+{
+ output_file_ << utilities::trim(gcode) << "\n";
+ //std::cout << utilities::trim(gcode) << "\n";
+ return 1;
+}
+
+int arc_welder::write_unwritten_gcodes_to_file()
+{
+ int size = unwritten_commands_.count();
+ std::string gcode_to_write;
+
+
+ for (int index = 0; index < size; index++)
+ {
+ // The the current unwritten position and remove it from the list
+ unwritten_command p = unwritten_commands_.pop_front();
+ bool has_e_coordinate = false;
+ std::string additional_comment = "";
+ double old_e = p.offset_e;
+ double new_e = old_e;
+ if (!p.is_extruder_relative && utilities::greater_than(abs(absolute_e_offset_), 0.0) &&
+ absolute_e_rewrite_commands_.find(p.command.command) != absolute_e_rewrite_commands_.end()
+ ){
+ // handle any absolute extrusion shift
+ // There is an offset, and we are in absolute E. Rewrite the gcode
+ parsed_command new_command = p.command;
+ new_command.parameters.clear();
+ has_e_coordinate = false;
+ for (unsigned int index = 0; index < p.command.parameters.size(); index++)
+ {
+ parsed_command_parameter p_cur_param = p.command.parameters[index];
+ if (p_cur_param.name == "E")
+ {
+ has_e_coordinate = true;
+ if (p_cur_param.value_type == 'U')
+ {
+ p_cur_param.value_type = 'F';
+ }
+ new_e = p.offset_e + absolute_e_offset_;
+ p_cur_param.double_value = new_e;
+
+ }
+ new_command.parameters.push_back(p_cur_param);
+ }
+
+
+ if (has_e_coordinate)
+ {
+ p.command = new_command;
+ }
+ }
+
+ write_gcode_to_file(p.to_string(has_e_coordinate, additional_comment));
+
+ }
+
+ return size;
+}
+
+std::string arc_welder::get_arc_gcode(double f, const std::string comment)
+{
+ // Write gcode to file
+ std::string gcode;
+ position* p_new_current_pos = p_source_position_->get_current_position_ptr();
+
+ if (p_new_current_pos->is_extruder_relative)
+ {
+ gcode = current_arc_.get_shape_gcode_relative(f);
+ }
+ else
+ {
+ // Make sure to add the absoulte e offset
+ gcode = current_arc_.get_shape_gcode_absolute(f, p_new_current_pos->get_current_extruder().get_offset_e());
+ }
+ if (comment.length() > 0)
+ {
+ gcode += ";" + comment;
+ }
+ return gcode;
+
+}
diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h
new file mode 100644
index 0000000..5e7db48
--- /dev/null
+++ b/ArcWelder/arc_welder.h
@@ -0,0 +1,99 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include
+#include
+#include
+#include "gcode_position.h"
+#include "position.h"
+#include "gcode_parser.h"
+#include "segmented_arc.h"
+#include
+#include
+#include "array_list.h"
+#include "unwritten_command.h"
+#include "logger.h"
+// define anti stutter class
+typedef bool(*progress_callback)(double percentComplete, double seconds_elapsed, double estimatedSecondsRemaining, int gcodesProcessed, int linesProcessed, int points_compressed, int arcs_created);
+
+class arc_welder
+{
+public:
+ arc_welder(std::string source_path, std::string target_path, logger * log, double resolution_mm, gcode_position_args args);
+ arc_welder(std::string source_path, std::string target_path, logger * log, double resolution_mm, bool g90_g91_influences_extruder, int buffer_size);
+ arc_welder(std::string source_path, std::string target_path, logger * log, double resolution_mm, bool g90_g91_influences_extruder, int buffer_size, progress_callback callback);
+ void set_logger_type(int logger_type);
+ virtual ~arc_welder();
+ void process();
+ double notification_period_seconds;
+protected:
+ virtual bool on_progress_(double percentComplete, double seconds_elapsed, double estimatedSecondsRemaining, int gcodesProcessed, int linesProcessed, int points_compressed, int arcs_created);
+private:
+ void reset();
+ static gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size);
+ progress_callback progress_callback_;
+ int process_gcode(parsed_command cmd, bool is_end);
+ int write_gcode_to_file(std::string gcode);
+ std::string get_arc_gcode(double f, const std::string comment);
+ std::string get_comment_for_arc();
+ int write_unwritten_gcodes_to_file();
+ std::string create_g92_e(double absolute_e);
+ std::string source_path_;
+ std::string target_path_;
+ double resolution_mm_;
+ double max_segments_;
+ gcode_position_args gcode_position_args_;
+ long file_size_;
+ int lines_processed_;
+ int gcodes_processed_;
+ int last_gcode_line_written_;
+ int points_compressed_;
+ int arcs_created_;
+ long get_file_size(const std::string& file_path);
+ double get_time_elapsed(double start_clock, double end_clock);
+ double get_next_update_time() const;
+ bool waiting_for_line_;
+ bool waiting_for_arc_;
+ array_list unwritten_commands_;
+ array_list undo_commands_;
+ segmented_arc current_arc_;
+ std::ofstream output_file_;
+
+ // We don't care about the printer settings, except for g91 influences extruder.
+ gcode_position * p_source_position_;
+ double absolute_e_offset_;
+ std::set absolute_e_rewrite_commands_;
+ gcode_parser parser_;
+ double absolute_e_offset_total_;
+ bool verbose_output_;
+ int logger_type_;
+ logger* p_logger_;
+ bool debug_logging_enabled_;
+ bool info_logging_enabled_;
+ bool verbose_logging_enabled_;
+ bool error_logging_enabled_;
+
+};
diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp
new file mode 100644
index 0000000..94c89fb
--- /dev/null
+++ b/ArcWelder/segmented_arc.cpp
@@ -0,0 +1,431 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "segmented_arc.h"
+#include "utilities.h"
+#include "segmented_shape.h"
+#include
+#include
+#include
+#include "math.h"
+#include
+
+segmented_arc::segmented_arc() : segmented_shape()
+{
+ min_segments_ = 3;
+ s_stream_ << std::fixed;
+}
+
+segmented_arc::segmented_arc(int max_segments, double resolution_mm) : segmented_shape(3, max_segments, resolution_mm)
+{
+ min_segments_ = 3;
+ s_stream_ << std::fixed;
+}
+
+segmented_arc::~segmented_arc()
+{
+}
+
+point segmented_arc::pop_front(double e_relative)
+{
+ e_relative_ -= e_relative;
+ if (points_.count() == min_segments_)
+ {
+ set_is_shape(false);
+ }
+ return points_.pop_front();
+}
+point segmented_arc::pop_back(double e_relative)
+{
+ e_relative_ -= e_relative;
+ return points_.pop_back();
+ if (points_.count() == min_segments_)
+ {
+ set_is_shape(false);
+ }
+}
+
+bool segmented_arc::is_shape()
+{
+ if (is_shape_)
+ {
+ arc a;
+ bool is_arc = try_get_arc(a);
+ return is_arc;
+ }
+ return is_shape_;
+}
+
+bool segmented_arc::try_add_point(point p, double e_relative)
+{
+
+ bool point_added = false;
+ // if we don't have enough segnemts to check the shape, just add
+ if (points_.count() > get_max_segments() - 1)
+ {
+ // Too many points, we can't add more
+ return false;
+ }
+ double distance = 0;
+ if (points_.count() > 0)
+ {
+ point p1 = points_[points_.count() - 1];
+ distance = utilities::get_cartesian_distance(p1.x, p1.y, p.x, p.y);
+ if (!utilities::is_equal(p1.z, p.z))
+ {
+ // Arcs require that z is equal for all points
+ //std::cout << " failed - z change.\n";
+
+ return false;
+ }
+
+ if (utilities::is_zero(distance))
+ {
+ // there must be some distance between the points
+ // to make an arc.
+ //std::cout << " failed - no distance change.\n";
+ return false;
+ }
+ /*else if (utilities::greater_than(distance, max_segment_length_))
+ {
+ // we can't make an arc if the distance between points
+ // is greater than the resolution.
+ return false;
+ }*/ // Test - see what happens without a max segment length.
+ }
+ if (points_.count() < min_segments_ - 1)
+ {
+ point_added = true;
+ }
+ else
+ {
+ // if we're here, we need to see if the new point can be included in the shape
+ point_added = try_add_point_internal(p, distance);
+ }
+ if (point_added)
+ {
+ points_.push_back(p);
+ original_shape_length_ += distance;
+ if (points_.count() > 1)
+ {
+ // Only add the relative distance to the second point on up.
+ e_relative_ += e_relative;
+ }
+ //std::cout << " success - " << points_.count() << " points.\n";
+ }
+ else if (points_.count() < min_segments_ && points_.count() > 1)
+ {
+ // If we haven't added a point, and we have exactly min_segments_,
+ // pull off the initial arc point and try again
+ point old_initial_point = points_.pop_front();
+ // We have to remove the distance and e relative value
+ // accumulated between the old arc start point and the new
+ point new_initial_point = points_[0];
+ original_shape_length_ -= utilities::get_cartesian_distance(old_initial_point.x, old_initial_point.y, new_initial_point.x, new_initial_point.y);
+ e_relative_ -= new_initial_point.e_relative;
+ //std::cout << " failed - removing start point and retrying current point.\n";
+ return try_add_point(p, e_relative);
+ }
+
+ return point_added;
+}
+
+bool segmented_arc::try_add_point_internal(point p, double pd)
+{
+ // If we don't have enough points (at least min_segments) return false
+ if (points_.count() < min_segments_ - 1)
+ return false;
+
+ // Create a test circle
+ circle test_circle;
+ bool circle_created;
+ // Find a point in the middle of our list for p2
+ int mid_point_index = ((points_.count() - 2) / 2)+1;
+ circle_created = circle::try_create_circle(points_[0], points_[mid_point_index], p, test_circle);
+
+ if (circle_created)
+ {
+
+ // If we got a circle, make sure all of the points fit within the tolerance.
+ bool circle_fits_points;
+
+ // the circle is new.. we have to test it now, which is expensive :(
+ circle_fits_points = does_circle_fit_points(test_circle, p, pd);
+ if (circle_fits_points)
+ {
+ arc_circle_ = test_circle;
+ }
+
+ // Only set is_shape if it goes from false to true
+ if (!is_shape())
+ set_is_shape(circle_fits_points);
+
+ return circle_fits_points;
+ }
+
+ //std::cout << " failed - could not create a circle from the points.\n";
+ return false;
+
+}
+
+bool segmented_arc::does_circle_fit_points(circle c, point p, double pd)
+{
+ // We know point 1 must fit (we used it to create the circle). Check the other points
+ // Note: We have not added the current point, but that's fine since it is guaranteed to fit too.
+ // If this works, it will be added.
+
+ double distance_from_center;
+ double difference_from_radius;
+
+ // Check the endpoints to make sure they fit the current circle
+ for (int index = 1; index < points_.count(); index++)
+ {
+ // Make sure the length from the center of our circle to the test point is
+ // at or below our max distance.
+ distance_from_center = utilities::get_cartesian_distance(points_[index].x, points_[index].y, c.center.x, c.center.y);
+ double difference_from_radius = abs(distance_from_center - c.radius);
+ if (utilities::greater_than(difference_from_radius, resolution_mm_))
+ {
+ //std::cout << " failed - end points do not lie on circle.\n";
+ return false;
+ }
+ }
+
+ /*
+ // Check the midpoints of the segments in the points_ to make sure they fit our circle.
+ for (int index = 0; index < points_.count() - 1; index++)
+ {
+ // Make sure the length from the center of our circle to the test point is
+ // at or below our max distance.
+ point midpoint = point::get_midpoint(points_[index], points_[index + 1]);
+ distance_from_center = utilities::get_cartesian_distance(midpoint.x, midpoint.y, c.center.x, c.center.y);
+ difference_from_radius = abs(distance_from_center - c.radius);
+ // Test allowing more play for the midpoints.
+ if (utilities::greater_than(difference_from_radius, resolution_mm_))
+ {
+ //std::cout << " failed - midpoints do not lie on circle.\n";
+ return false;
+ }
+ }
+ */
+ // Check the point perpendicular from the segment to the circle's center, if any such point exists
+ for (int index = 0; index < points_.count() - 1; index++)
+ {
+ point point_to_test;
+ if (segment::get_closest_perpendicular_point(points_[index], points_[index + 1], c.center, point_to_test))
+ {
+ distance_from_center = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, c.center.x, c.center.y);
+ difference_from_radius = abs(distance_from_center - c.radius);
+ // Test allowing more play for the midpoints.
+ if (utilities::greater_than(difference_from_radius, resolution_mm_))
+ {
+ return false;
+ }
+ }
+
+ }
+
+ // Check the midpoint of the new point and the final point
+ point point_to_test;
+ if (segment::get_closest_perpendicular_point(points_[points_.count() - 1], p, c.center, point_to_test))
+ {
+ distance_from_center = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, c.center.x, c.center.y);
+ difference_from_radius = abs(distance_from_center - c.radius);
+ // Test allowing more play for the midpoints.
+ if (utilities::greater_than(difference_from_radius, resolution_mm_))
+ {
+ return false;
+ }
+ }
+
+ // get the current arc and compare the total length to the original length
+ arc a;
+ return try_get_arc(c, p, pd, a );
+ /*
+ if (!a.is_arc || utilities::greater_than(abs(a.length - (original_shape_length_ + pd)), resolution_mm_*2))
+ {
+ //std::cout << " failed - final lengths do not match.\n";
+ return false;
+ }
+ return true;
+ */
+}
+
+bool segmented_arc::try_get_arc(arc & target_arc)
+{
+ int mid_point_index = ((points_.count() - 2) / 2) + 1;
+ return arc::try_create_arc(arc_circle_, points_[0], points_[mid_point_index], points_[points_.count() - 1], original_shape_length_, resolution_mm_, target_arc);
+}
+
+bool segmented_arc::try_get_arc(circle& c, point endpoint, double additional_distance, arc &target_arc)
+{
+ int mid_point_index = ((points_.count() - 1) / 2) + 1;
+ return arc::try_create_arc(c, points_[0], points_[mid_point_index], endpoint, original_shape_length_ + additional_distance, resolution_mm_, target_arc);
+}
+/*
+
+std::string segmented_arc::get_shape_gcode_absolute(double f, double e_abs_start)
+{
+
+ s_stream_.clear();
+ s_stream_.str("");
+ arc c;
+ try_get_arc(c);
+
+ double new_extrusion;
+ // get the original ratio of filament extruded to length, but not for retractions
+ if (utilities::greater_than(e_relative_, 0))
+ {
+ double extrusion_per_mm = e_relative_ / original_shape_length_;
+ new_extrusion = c.length * extrusion_per_mm;
+ }
+ else
+ {
+ new_extrusion = e_relative_;
+ }
+
+
+ double i = c.center.x - c.start_point.x;
+ double j = c.center.y - c.start_point.y;
+ if (utilities::less_than(c.angle_radians, 0))
+ {
+ s_stream_ << "G2";
+ }
+ else
+ {
+ s_stream_ << "G3";
+ }
+ s_stream_ << std::setprecision(3);
+ s_stream_ << " X" << c.end_point.x << " Y" << c.end_point.y << " I" << i << " J" << j;
+ // Do not output for travel movements
+ if (e_relative_ != 0)
+ {
+ s_stream_ << std::setprecision(5);
+ s_stream_ << " E" << e_abs_start + new_extrusion;
+ }
+
+ if (utilities::greater_than(f, 0))
+ {
+ s_stream_ << std::setprecision(0) << " F" << f;
+ }
+ return s_stream_.str();
+}*/
+
+std::string segmented_arc::get_shape_gcode_absolute(double f, double e_abs_start)
+{
+ arc c;
+ try_get_arc(c);
+
+ double new_extrusion;
+ // get the original ratio of filament extruded to length, but not for retractions
+ if (utilities::greater_than(e_relative_, 0))
+ {
+ double extrusion_per_mm = e_relative_ / original_shape_length_;
+ new_extrusion = c.length * extrusion_per_mm;
+ }
+ else
+ {
+ new_extrusion = e_relative_;
+ }
+ double i = c.center.x - c.start_point.x;
+ double j = c.center.y - c.start_point.y;
+ // Here is where the performance part kicks in (these are expensive calls) that makes things a bit ugly.
+ // there are a few cases we need to take into consideration before choosing our sprintf string
+ if (utilities::less_than(c.angle_radians, 0))
+ {
+ // G2
+ if (e_relative_ != 0)
+ {
+ double e = e_abs_start + new_extrusion;
+ // Add E param
+ if (utilities::greater_than_or_equal(f, 1))
+ {
+ // Add F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G2 X%.3f Y%.3f I%.3f J%.3f E%.5f F%.0f", c.end_point.x, c.end_point.y, i, j, e, f);
+ }
+ else
+ {
+ // No F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G2 X%.3f Y%.3f I%.3f J%.3f E%.5f", c.end_point.x, c.end_point.y, i, j, e);
+ }
+ }
+ else
+ {
+ // No E param
+ // Add E param
+ if (utilities::greater_than_or_equal(f, 1))
+ {
+ // Add F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G2 X%.3f Y%.3f I%.3f J%.3f F%.0f", c.end_point.x, c.end_point.y, i, j, f);
+ }
+ else
+ {
+ // No F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G2 X%.3f Y%.3f I%.3f J%.3f", c.end_point.x, c.end_point.y, i, j);
+ }
+ }
+ }
+ else
+ {
+ // G3
+ if (e_relative_ != 0)
+ {
+ double e = e_abs_start + new_extrusion;
+ // Add E param
+ if (utilities::greater_than_or_equal(f, 1))
+ {
+ // Add F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G3 X%.3f Y%.3f I%.3f J%.3f E%.5f F%.0f", c.end_point.x, c.end_point.y, i, j, e, f);
+ }
+ else
+ {
+ // No F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G3 X%.3f Y%.3f I%.3f J%.3f E%.5f", c.end_point.x, c.end_point.y, i, j, e);
+ }
+ }
+ else
+ {
+ // No E param
+ // Add E param
+ if (utilities::greater_than_or_equal(f, 1))
+ {
+ // Add F param
+ snprintf(gcode_buffer_, sizeof(gcode_buffer_), "G3 X%.3f Y%.3f I%.3f J%.3f F%.0f", c.end_point.x, c.end_point.y, i, j, f);
+ }
+ else
+ {
+ // No F param
+ snprintf(gcode_buffer_, GCODE_CHAR_BUFFER_SIZE, "G3 X%.3f Y%.3f I%.3f J%.3f", c.end_point.x, c.end_point.y, i, j);
+ }
+ }
+ }
+ return std::string(gcode_buffer_);
+
+}
+
+std::string segmented_arc::get_shape_gcode_relative(double f)
+{
+ return get_shape_gcode_absolute(f, 0.0);
+}
diff --git a/ArcWelder/segmented_arc.h b/ArcWelder/segmented_arc.h
new file mode 100644
index 0000000..02a8482
--- /dev/null
+++ b/ArcWelder/segmented_arc.h
@@ -0,0 +1,58 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include "segmented_shape.h"
+#include
+#include
+
+#define GCODE_CHAR_BUFFER_SIZE 100
+class segmented_arc :
+ public segmented_shape
+{
+public:
+ segmented_arc();
+ segmented_arc(int max_segments, double resolution_mm);
+ virtual ~segmented_arc();
+ virtual bool try_add_point(point p, double e_relative);
+ virtual std::string get_shape_gcode_absolute(double f, double e_abs_start);
+ virtual std::string get_shape_gcode_relative(double f);
+ virtual bool is_shape();
+ point pop_front(double e_relative);
+ point pop_back(double e_relative);
+ bool try_get_arc(arc & target_arc);
+ // static gcode buffer
+
+private:
+ char gcode_buffer_[GCODE_CHAR_BUFFER_SIZE];
+ bool try_add_point_internal(point p, double pd);
+ bool does_circle_fit_points(circle c, point p, double additional_distance);
+ bool try_get_arc(circle& c, point endpoint, double additional_distance, arc & target_arc);
+ int min_segments_;
+ circle arc_circle_;
+ int test_count_ = 0;
+ std::ostringstream s_stream_;
+};
+
diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp
new file mode 100644
index 0000000..4426e6b
--- /dev/null
+++ b/ArcWelder/segmented_shape.cpp
@@ -0,0 +1,422 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "segmented_shape.h"
+
+#include
+#include
+#pragma region Operators for Vector and Point
+point operator +(point lhs, const vector rhs) {
+ point p(
+ lhs.x + rhs.x,
+ lhs.y + rhs.y,
+ lhs.z + rhs.z,
+ lhs.e_relative + rhs.e_relative
+ );
+ return p;
+}
+
+point operator -(point lhs, const vector rhs) {
+ return point(
+ lhs.x - rhs.x,
+ lhs.y - rhs.y,
+ lhs.z - rhs.z,
+ lhs.e_relative - rhs.e_relative
+ );
+}
+
+vector operator -(point& lhs, point& rhs) {
+ return vector(
+ lhs.x - rhs.x,
+ lhs.y - rhs.y,
+ lhs.z - rhs.z
+ );
+}
+
+vector operator *(vector lhs, const double& rhs) {
+ return vector(
+ lhs.x*rhs,
+ lhs.y*rhs,
+ lhs.z*rhs
+ );
+}
+#pragma endregion Operators for Vector and Point
+
+#pragma region Point Functions
+point point::get_midpoint(point p1, point p2)
+{
+ double x = (p1.x + p2.x) / 2.0;
+ double y = (p1.y + p2.y) / 2.0;
+ double z = (p1.z + p2.z) / 2.0;
+
+ return point(x, y, z, 0);
+}
+#pragma endregion Point Functions
+
+#pragma region Segment Functions
+bool segment::get_closest_perpendicular_point(point c, point &d)
+{
+ return segment::get_closest_perpendicular_point(p1, p2, c, d);
+}
+
+bool segment::get_closest_perpendicular_point(point p1, point p2, point c, point& d)
+{
+ // [(Cx - Ax)(Bx - Ax) + (Cy - Ay)(By - Ay)] / [(Bx - Ax) ^ 2 + (By - Ay) ^ 2]
+ double num = (c.x - p1.x)*(p2.x - p1.x) + (c.y - p1.y)*(p2.y - p1.y);
+ double denom = (pow((p2.x - p1.x), 2) + pow((p2.y - p1.y), 2));
+ double t = num / denom;
+
+ // We're considering this a failure if t == 0 or t==1 within our tolerance. In that case we hit the endpoint, which is OK.
+ if (utilities::less_than_or_equal(t, 0, CIRCLE_FLOATING_POINT_TOLERANCE) || utilities::greater_than_or_equal(t, 1, CIRCLE_FLOATING_POINT_TOLERANCE))
+ return false;
+
+ d.x = p1.x + t * (p2.x - p1.x);
+ d.y = p1.y + t * (p2.y - p1.y);
+
+ return true;
+}
+
+#pragma endregion
+
+#pragma region Vector Functions
+double vector::get_magnitude()
+{
+ return sqrt(x * x + y * y + z * z);
+}
+
+double vector::cross_product_magnitude(vector v1, vector v2)
+{
+ return (v1.x * v2.y - v1.y * v2.x);
+}
+#pragma endregion Vector Functions
+
+#pragma region Distance Calculation Source
+// Distance Calculation code taken from the following source:
+// Copyright for distance calculations:
+// Copyright 2001 softSurfer, 2012 Dan Sunday
+// This code may be freely used, distributed and modified for any purpose
+// providing that this copyright notice is included with it.
+// SoftSurfer makes no warranty for this code, and cannot be held
+// liable for any real or imagined damage resulting from its use.
+// Users of this code must verify correctness for their application.
+// dot product (3D) which allows vector operations in arguments
+#define dot(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
+#define norm(v) sqrt(dot(v,v)) // norm = length of vector
+#define d(u,v) norm(u-v) // distance = norm of difference
+
+double distance_from_segment(segment s, point p)
+{
+ vector v = s.p2 - s.p1;
+ vector w = p - s.p1;
+
+ double c1 = dot(w, v);
+ if (c1 <= 0)
+ return d(p, s.p1);
+
+ double c2 = dot(v, v);
+ if (c2 <= c1)
+ return d(p, s.p2);
+
+ double b = c1 / c2;
+ point pb = s.p1 + (v * b);
+ return d(p, pb);
+}
+
+#pragma endregion Distance Calculation Source
+
+
+#pragma region Circle Functions
+bool circle::is_point_on_circle(point p, double resolution_mm)
+{
+ // get the difference between the point and the circle's center.
+ double difference = abs(utilities::get_cartesian_distance(p.x, p.y, center.x, center.y) - radius);
+ return utilities::less_than(difference, resolution_mm, CIRCLE_FLOATING_POINT_TOLERANCE);
+}
+
+bool circle::try_create_circle(point p1, point p2, point p3, circle& new_circle)
+{
+ double x1 = p1.x;
+ double y1 = p1.y;
+ double x2 = p2.x;
+ double y2 = p2.y;
+ double x3 = p3.x;
+ double y3 = p3.y;
+
+ double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
+
+ if (utilities::is_zero(a, CIRCLE_FLOATING_POINT_TOLERANCE))
+ {
+ return false;
+ }
+
+
+ double b = (x1 * x1 + y1 * y1) * (y3 - y2)
+ + (x2 * x2 + y2 * y2) * (y1 - y3)
+ + (x3 * x3 + y3 * y3) * (y2 - y1);
+
+ double c = (x1 * x1 + y1 * y1) * (x2 - x3)
+ + (x2 * x2 + y2 * y2) * (x3 - x1)
+ + (x3 * x3 + y3 * y3) * (x1 - x2);
+
+ double x = -b / (2.0 * a);
+ double y = -c / (2.0 * a);
+
+ new_circle.center.x = x;
+ new_circle.center.y = y;
+ new_circle.center.z = p1.z;
+ new_circle.radius = utilities::get_cartesian_distance(x, y, x1, y1);
+ return true;
+}
+double circle::get_radians(point p1, point p2)
+{
+ double distance_sq = pow(utilities::get_cartesian_distance(p1.x, p1.y, p2.x, p2.y), 2.0);
+ double two_r_sq = 2.0 * radius * radius;
+ return acos((two_r_sq - distance_sq) / two_r_sq);
+}
+
+point circle::get_closest_point(point p)
+{
+ vector v = p - center;
+ double mag = v.get_magnitude();
+ double px = center.x + v.x / mag * radius;
+ double py = center.y + v.y / mag * radius;
+ double pz = center.z + v.z / mag * radius;
+ return point(px, py, pz, 0);
+}
+#pragma endregion Circle Functions
+
+#pragma region Arc Functions
+bool arc::try_create_arc(circle c, point start_point, point mid_point, point end_point, double approximate_length, double resolution, arc& target_arc)
+{
+ point p1 = c.get_closest_point(start_point);
+ point p2 = c.get_closest_point(mid_point);
+ point p3 = c.get_closest_point(end_point);
+ // Get the radians between p1 and p2 (short angle)
+ double p1_p2_rad = c.get_radians(p1, p2);
+ double p2_p3_rad = c.get_radians(p2, p3);
+ double p3_p1_rad = c.get_radians(p3, p1);
+
+ bool found_angle = false;
+ double angle_radians = 0;
+ double angle_1, angle_2;
+ if (utilities::is_equal(p1_p2_rad + p2_p3_rad + p3_p1_rad, 2 * PI_DOUBLE, CIRCLE_FLOATING_POINT_TOLERANCE))
+ {
+ found_angle = true;
+ angle_1 = p1_p2_rad;
+ angle_2 = p2_p3_rad;
+ }
+ else if (utilities::is_equal(p1_p2_rad + p2_p3_rad + (2 * PI_DOUBLE - p3_p1_rad), 2 * PI_DOUBLE, CIRCLE_FLOATING_POINT_TOLERANCE))
+ {
+ found_angle = true;
+ angle_1 = p2_p3_rad;
+ angle_2 = p1_p2_rad;
+ }
+ else
+ {
+ double p1_p2_rad_lg = (2 * PI_DOUBLE - p1_p2_rad);
+ if (utilities::is_equal(p1_p2_rad_lg + p2_p3_rad + p3_p1_rad, 2 * PI_DOUBLE, CIRCLE_FLOATING_POINT_TOLERANCE))
+ {
+ found_angle = true;
+ angle_1 = p1_p2_rad_lg;
+ angle_2 = p2_p3_rad;
+ }
+ else
+ {
+ double p2_p3_rad_lg = (2 * PI_DOUBLE - p2_p3_rad);
+ if (utilities::is_equal(p1_p2_rad + p2_p3_rad_lg + p3_p1_rad, 2 * PI_DOUBLE, CIRCLE_FLOATING_POINT_TOLERANCE))
+ {
+ found_angle = true;
+ angle_1 = p1_p2_rad;
+ angle_2 = p2_p3_rad_lg;
+ }
+ }
+ }
+ if (!found_angle)
+ return false; // No angle could be found, exit.
+ angle_radians = angle_1 + angle_2;
+ double length = angle_radians * c.radius;
+ if (!utilities::is_equal(length, approximate_length, resolution))
+ return false;
+
+ // Very small angles can't be relied upon to calculate the sign of the arc (clockwise vs anticlockwise)
+ if (angle_radians < MIN_ALLOWED_ARC_THETA)
+ {
+ return false;
+ }
+
+ // Calculate the sign of the angle. This should be accurate now that we have filtered out small angles and exited due to lengh mismatches
+ vector v1 = p1 - p2;
+ vector v2 = p3 - p2;
+ // Try to make a reasonable guess about the angle's direction. This works well unless the the angle is very small
+ double magnitude1 = vector::cross_product_magnitude(v1, v2);
+ // We can't use our utility compare (utility::greater_that) here, else we will lose
+ // very important resolution information
+ bool is_clockwise = false;
+
+ if (magnitude1 > 0.0)
+ {
+ is_clockwise = true;
+ }
+ // If the calculated length isn't within the resolution, exit
+ if (is_clockwise)
+ angle_radians *= -1.0f;
+
+
+ target_arc.center.x = c.center.x;
+ target_arc.center.y = c.center.y;
+ target_arc.center.z = c.center.z;
+ target_arc.radius = c.radius;
+ target_arc.start_point = start_point;
+ target_arc.end_point = end_point;
+ target_arc.length = length;
+ target_arc.angle_radians = angle_radians;
+ return true;
+
+}
+
+#pragma endregion
+
+segmented_shape::segmented_shape() : points_(50)
+{
+ max_segments_ = 50;
+ resolution_mm_ = 0.0250;
+ e_relative_ = 0;
+ is_shape_ = false;
+ min_segments_ = 3;
+ original_shape_length_ = 0;
+ is_extruding_ = true;
+}
+segmented_shape::segmented_shape(int min_segments, int max_segments, double resolution_mm) : points_(max_segments)
+{
+ max_segments_ = max_segments;
+ resolution_mm_ = resolution_mm / 2.0; // divide by 2 because it is + or - 1/2 of the desired resolution.
+ e_relative_ = 0;
+ is_shape_ = false;
+ min_segments_ = min_segments;
+ original_shape_length_ = 0;
+ is_extruding_ = true;
+}
+
+segmented_shape::~segmented_shape()
+{
+
+}
+
+bool segmented_shape::is_extruding()
+{
+ return is_extruding_;
+}
+segmented_shape& segmented_shape::operator=(const segmented_shape& obj)
+{
+ points_.clear();
+ if (obj.max_segments_ != max_segments_)
+ {
+ max_segments_ = obj.max_segments_;
+
+ points_.resize(max_segments_);
+ }
+ points_.copy(obj.points_);
+
+ original_shape_length_ = obj.original_shape_length_;
+ e_relative_ = obj.e_relative_;
+ is_shape_ = obj.is_shape_;
+ max_segments_ = obj.max_segments_;
+ resolution_mm_ = obj.resolution_mm_;
+ return *this;
+}
+
+int segmented_shape::get_num_segments()
+{
+ return points_.count();
+}
+
+double segmented_shape::get_shape_length()
+{
+ return original_shape_length_;
+}
+
+double segmented_shape::get_shape_e_relative()
+{
+ return e_relative_;
+}
+
+void segmented_shape::clear()
+{
+ points_.clear();
+ is_shape_ = false;
+ e_relative_ = 0;
+ original_shape_length_ = 0;
+}
+bool segmented_shape::is_shape()
+{
+ // return the pre-calculated value. This should be updated by the plugin;
+ return is_shape_;
+}
+void segmented_shape::set_is_shape(bool value)
+{
+ is_shape_ = value;
+}
+
+int segmented_shape::get_min_segments()
+{
+ return min_segments_;
+}
+int segmented_shape::get_max_segments()
+{
+ return max_segments_;
+}
+
+double segmented_shape::get_resolution_mm()
+{
+ return resolution_mm_;
+}
+void segmented_shape::set_resolution_mm(double resolution_mm)
+{
+ resolution_mm_ = resolution_mm;
+
+}
+point segmented_shape::pop_front()
+{
+ return points_.pop_front();
+}
+point segmented_shape::pop_back()
+{
+ return points_.pop_back();
+}
+
+bool segmented_shape::try_add_point(point p, double e_relative)
+{
+ throw std::exception();
+}
+
+std::string segmented_shape::get_shape_gcode_absolute(double e_abs_start)
+{
+ throw std::exception();
+}
+
+std::string segmented_shape::get_shape_gcode_relative()
+{
+ throw std::exception();
+}
\ No newline at end of file
diff --git a/ArcWelder/segmented_shape.h b/ArcWelder/segmented_shape.h
new file mode 100644
index 0000000..5a9b64c
--- /dev/null
+++ b/ArcWelder/segmented_shape.h
@@ -0,0 +1,192 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include
+#include
+#define PI_DOUBLE 3.14159265358979323846
+#define CIRCLE_FLOATING_POINT_TOLERANCE 0.0000000001
+#include
+#include "utilities.h"
+#include "array_list.h"
+// The minimum theta value allowed between any two arc in order for an arc to be
+// created. This prevents sign calculation issues for very small values of theta
+
+#define MIN_ALLOWED_ARC_THETA 0.0001f // Safe value for full theta
+//#define MIN_ALLOWED_ARC_THETA 0.0000046875f // Lowest discovered value for full theta
+
+struct point
+{
+public:
+ point() {
+ x = 0;
+ y = 0;
+ z = 0;
+ e_relative = 0;
+ }
+ point(double p_x, double p_y, double p_z, double p_e_relative) {
+ x = p_x;
+ y = p_y;
+ z = p_z;
+ e_relative = p_e_relative;
+ }
+ double x;
+ double y;
+ double z;
+ double e_relative;
+ static point get_midpoint(point p1, point p2);
+};
+
+struct segment
+{
+ segment()
+ {
+
+ }
+ segment(point p_1, point p_2)
+ {
+ p1.x = p_1.x;
+ p1.y = p_1.y;
+ p1.z = p_1.z;
+
+ p2.x = p_2.x;
+ p2.y = p_2.y;
+ p2.z = p_2.z;
+ }
+ point p1;
+ point p2;
+
+ bool get_closest_perpendicular_point(point c, point& d);
+ static bool get_closest_perpendicular_point(point p1, point p2, point c, point& d);
+};
+
+struct vector : point
+{
+ vector() {
+ x = 0;
+ y = 0;
+ z = 0;
+ }
+ vector(double p_x, double p_y, double p_z) {
+ x = p_x;
+ y = p_y;
+ z = p_z;
+ }
+
+ double get_magnitude();
+ static double cross_product_magnitude(vector v1, vector v2);
+
+};
+
+struct circle {
+ circle() {
+ center.x = 0;
+ center.y = 0;
+ center.z = 0;
+ radius = 0;
+ };
+ circle(point p, double r)
+ {
+ center.x = p.x;
+ center.y = p.y;
+ center.z = p.z;
+ radius = r;
+ }
+ point center;
+ double radius;
+
+ bool is_point_on_circle(point p, double resolution_mm);
+ static bool try_create_circle(point p1, point p2, point p3, circle& new_circle);
+
+ double get_radians(point p1, point p2);
+
+ point get_closest_point(point p);
+};
+
+struct arc : circle
+{
+ arc() {
+ center.x = 0;
+ center.y = 0;
+ center.z = 0;
+ radius = 0;
+ length = 0;
+ angle_radians = 0;
+ start_point.x = 0;
+ start_point.y = 0;
+ start_point.z = 0;
+ end_point.x = 0;
+ end_point.y = 0;
+ end_point.z = 0;
+ is_arc = false;
+
+ }
+
+ bool is_arc;
+ double length;
+ double angle_radians;
+ point start_point;
+ point end_point;
+ static bool try_create_arc(circle c, point start_point, point mid_point, point end_point, double approximate_length, double resolution, arc& target_arc);
+
+};
+double distance_from_segment(segment s, point p);
+
+class segmented_shape
+{
+public:
+ segmented_shape();
+ segmented_shape(int min_segments, int max_segments, double resolution_mm);
+ segmented_shape& operator=(const segmented_shape& pos);
+ virtual ~segmented_shape();
+ int get_num_segments();
+ int get_min_segments();
+ int get_max_segments();
+ double get_resolution_mm();
+ double get_shape_length();
+ double get_shape_e_relative();
+ void set_resolution_mm(double resolution_mm);
+ virtual bool is_shape();
+ // public virtual functions
+ virtual void clear();
+ virtual point pop_front();
+ virtual point pop_back();
+ virtual bool try_add_point(point p, double e_relative);
+ virtual std::string get_shape_gcode_absolute(double e_abs_start);
+ virtual std::string get_shape_gcode_relative();
+ bool is_extruding();
+protected:
+ array_list points_;
+ void set_is_shape(bool value);
+ int min_segments_;
+ double original_shape_length_;
+ double e_relative_;
+ bool is_extruding_;
+ double resolution_mm_;
+ bool is_shape_;
+private:
+
+ int max_segments_;
+
+};
diff --git a/ArcWelder/unwritten_command.h b/ArcWelder/unwritten_command.h
new file mode 100644
index 0000000..5591dbe
--- /dev/null
+++ b/ArcWelder/unwritten_command.h
@@ -0,0 +1,62 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Library
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include "parsed_command.h"
+#include "position.h"
+struct unwritten_command
+{
+ unwritten_command() {
+ is_extruder_relative = false;
+ e_relative = 0;
+ offset_e = 0;
+ }
+ unwritten_command(parsed_command &cmd, bool is_relative) {
+ is_relative = false;
+ command = cmd;
+ }
+ unwritten_command(position* p) {
+ e_relative = p->get_current_extruder().e_relative;
+ offset_e = p->get_current_extruder().get_offset_e();
+ is_extruder_relative = p->is_extruder_relative;
+ command = p->command;
+ }
+ bool is_extruder_relative;
+ double e_relative;
+ double offset_e;
+ parsed_command command;
+
+ std::string to_string(bool rewrite, std::string additional_comment)
+ {
+ command.comment.append(additional_comment);
+
+ if (rewrite)
+ {
+ return command.rewrite_gcode_string();
+ }
+
+ return command.to_string();
+ }
+};
+
diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp
new file mode 100644
index 0000000..ebc39b5
--- /dev/null
+++ b/ArcWelderConsole/ArcWelderConsole.cpp
@@ -0,0 +1,236 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Console Application
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#include "ArcWelderConsole.h"
+#include
+#include
+#include
+#include "arc_welder.h"
+#include "gcode_position.h"
+int main(int argc, char* argv[])
+{
+ std::string info = "Arc Welder: Anti-Stutter v0.1.rc1.dev0\nReduces the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3)\nCopyright(C) 2020 - Brad Hochgesang\n";
+ std::cout << info;
+ // General Strings
+ std::string usage_string = "Usage: ";
+ std::string default_value_string = "Default Value: ";
+ std::string usage_example_string = "Example Usage: ";
+ // Resolution messages
+ std::string resolution_parameter = "--resolution-mm";
+ std::string resolution_usage = resolution_parameter + " {float}";
+ std::string resolution_description = "The resolution in mm of the of the output. Determines the maximum tool path deviation allowed during conversion.";
+ std::string resolution_default_value = "0.05 (in millimeters)";
+ std::string resolution_usage_example = resolution_parameter + " 0.1";
+ // g90/g91 influences extruder messages
+ std::string g90_influences_extruder_parameter = "--g90-influences-extruder";
+ std::string g90_influences_extruder_usage = g90_influences_extruder_parameter + " {true/false}";
+ std::string g90_influences_extruder_description = "If true, G90 and G91 influence the E axis. This can affect retraction and extrusion calculations depending on the gcode.";
+ std::string g90_influences_extruder_default_value = "false";
+ std::string g90_influences_extruder_usage_example = g90_influences_extruder_parameter + " true";
+ // show_progress messages
+ std::string show_progress_parameter = "--show-progress";
+ std::string show_progress_usage = show_progress_parameter + " {true/false}";
+ std::string show_progress_description = "Display a periodic progress message every 1 second.";
+ std::string show_progress_default_value = "true";
+ std::string show_progress_usage_example = show_progress_parameter + " false";
+ // log level messages
+ std::string log_level_parameter = "--log-level";
+ std::string log_level_usage = log_level_parameter + " {NOSET/VERBOSE/DEBUG/INFO/WARNING/ERROR/CRITICAL}";
+ std::string log_level_usage_description = "Sets console log level. DEBUG, VERBOSE, AND NOSET will produce a HUGE amount of output, and will slow processing considerably. Errors and Exceptions will be logged to stderr.";
+ std::string log_level_usage_default_value = "ERROR";
+ std::string log_level_usage_example = log_level_parameter + " DEBUG";
+
+ std::stringstream usage_message_stream;
+ usage_message_stream << usage_string << argv[0] << " {SOURCE_PATH} {DESTINATION_PATH} {optional arguments}\n";
+ usage_message_stream << usage_example_string << argv[0] << "\"c:\\source_file.gcode\" \"c:\\target_file.gcode\" -resolution 0.05 -g90-91-influences-extruder false -log-level DEBUG\n";
+ usage_message_stream << "********************\n";
+ usage_message_stream << "Argument Description:\n";
+ usage_message_stream << "SOURCE_PATH:\n\tThe full path of the gcode file to compress. This is a required argument\n";
+ usage_message_stream << "TARGET_PATH:\n\tThe full path of the compressed gcode output. Any existing target file will be overwritten!\n";
+ usage_message_stream << resolution_usage << "\n\t" << resolution_description << "\n\t" << default_value_string << resolution_default_value << "\n\t" << usage_example_string << resolution_usage_example << "\n";
+ usage_message_stream << g90_influences_extruder_usage << "\n\t" << g90_influences_extruder_description << "\n\t" << default_value_string << g90_influences_extruder_default_value << "\n\t" << usage_example_string << g90_influences_extruder_usage_example << "\n";
+ usage_message_stream << show_progress_parameter << "\n\t" << show_progress_description << "\n\t" << default_value_string << show_progress_default_value << "\n\t" << usage_example_string << show_progress_usage_example << "\n";
+ usage_message_stream << log_level_usage << "\n\t" << log_level_usage_description << "\n\t" << default_value_string << log_level_usage_default_value << "\n\t" << usage_example_string << log_level_usage_example << "\n";
+
+ // Ensure at least 3 parameters, including the cmd.
+ if (argc < 3)
+ {
+ if (argc > 1)
+ std::cerr << "Error executing " << argv[0] << ": Invalid number of arguments.\n";
+ else
+ std::cout << argv[0] << " - Displaying Help\n";
+ std::cout << usage_message_stream.str();
+ if (argc > 1)
+ return 1;
+ return -1;
+ }
+ // get the source and target path
+ std::string source_file_path = argv[1];
+ std::string target_file_apth = argv[2];
+ double resolution_mm = 0.05;
+ bool g90_g91_influences_extruder = false;
+ bool show_progress = true;
+ int log_level_value = 40;
+ // Extract opotional parameters
+ std::vector sources;
+ for (int i = 3; i < argc; ++i) {
+ std::string parameter;
+ for (unsigned int letter_index = 0; letter_index < strlen(argv[i]); letter_index++)
+ parameter += tolower(argv[i][letter_index]);
+
+ if (parameter == resolution_parameter) {
+ // make sure we have another argument after the flag
+ if (i + 1 >= argc)
+ {
+ std::cerr << "The " << resolution_parameter <<" parameter requires a float value.\n\t" << usage_string << resolution_usage << "\n\t" << usage_example_string << resolution_usage_example << "\n";
+ return 1;
+ }
+ i++; // increment the index to extract the parameter's value
+ try
+ {
+ resolution_mm = std::stod(argv[i]);
+ }
+ catch (std::invalid_argument) {
+ std::cerr << "Unable to convert the " << resolution_parameter << " value '" << argv[i] << "' to a float.\n\t" << usage_string << resolution_usage << "\n\t" << usage_example_string << resolution_usage_example << "\n";
+ return 1;
+ }
+ }
+ else if (parameter == g90_influences_extruder_parameter)
+ {
+ // make sure we have another argument after the flag
+ if (i + 1 >= argc)
+ {
+ std::cerr << "The " << g90_influences_extruder_parameter << " parameter requires a boolean value.\n\t" << usage_string << g90_influences_extruder_usage << "\n\t" << usage_example_string << g90_influences_extruder_usage_example << "\n";
+ return 1;
+ }
+ i++; // increment the index to extract the parameter's value
+ std::string g90_g91_influences_extruder_string;
+
+ for (unsigned int letter_index = 0; letter_index < strlen(argv[i]); letter_index++)
+ g90_g91_influences_extruder_string += toupper(argv[i][letter_index]);
+
+ if (g90_g91_influences_extruder_string != "TRUE" && g90_g91_influences_extruder_string != "FALSE")
+ {
+ std::cerr << "Unable to convert the " << g90_influences_extruder_parameter << " value '" << argv[i] << "' to a boolean.\n\t" << usage_string << g90_influences_extruder_usage << "\n\t" << usage_example_string << g90_influences_extruder_usage_example << "\n";
+ return 1;
+ }
+ g90_g91_influences_extruder = g90_g91_influences_extruder_string == "TRUE";
+ }
+ else if (parameter == show_progress_parameter)
+ {
+ // make sure we have another argument after the flag
+ if (i + 1 >= argc)
+ {
+ std::cerr << "The " << show_progress_parameter << " parameter requires a boolean value.\n\t" << usage_string << show_progress_usage << "\n\t" << usage_example_string << show_progress_usage_example << "\n";
+ return 1;
+ }
+ i++; // increment the index to extract the parameter's value
+ std::string show_progress_string;
+ for (unsigned int letter_index = 0; letter_index < strlen(argv[i]); letter_index++)
+ show_progress_string += toupper(argv[i][letter_index]);
+ if (show_progress_string != "TRUE" && show_progress_string != "FALSE")
+ {
+ std::cerr << "Unable to convert the " << show_progress_parameter << " value '" << argv[i] << "' to a boolean.\n\t" << usage_string << show_progress_usage << "\n\t" << usage_example_string << show_progress_usage_example << "\n";
+ return 1;
+ }
+ show_progress = show_progress_string == "TRUE";
+ }
+ else if (parameter == log_level_parameter)
+ {
+ // make sure we have another argument after the flag
+ if (i + 1 >= argc)
+ {
+ std::cerr << "The " << log_level_parameter << " parameter requires a valid log level string.\n\t" << usage_string << log_level_usage << "\n\t" << usage_example_string << log_level_usage_example << "\n";
+ return 1;
+ }
+ i++; // increment the index to extract the parameter's value
+ std::string log_level_name;
+ for (unsigned int letter_index = 0; letter_index < strlen(argv[i]); letter_index++)
+ log_level_name += toupper(argv[i][letter_index]);
+
+ // Ensure the log level name is valid
+ log_level_value = -1;
+ for (unsigned int log_name_index = 0; log_name_index < log_level_names.size(); log_name_index++)
+ {
+ if (log_level_name == log_level_names[log_name_index])
+ {
+ log_level_value = log_level_values[log_name_index];
+ break;
+ }
+ }
+ if (log_level_value == -1)
+ {
+ std::cerr << "Unable to convert the " << log_level_parameter << " value '" << argv[i] << "' to a valid log level string.\n\t" << usage_string << log_level_usage << "\n\t" << usage_example_string << log_level_usage_example << "\n";
+ return 1;
+ }
+
+ }
+ else {
+ std::cerr << "An unknown parameter '" << parameter << "' was received.\n" << usage_message_stream.str();
+ return 1;
+ }
+ }
+
+ std::vector log_names;
+ log_names.push_back("arc_welder.gcode_conversion");
+ std::vector log_levels;
+ log_levels.push_back(log_levels::DEBUG);
+ logger* p_logger = new logger(log_names, log_levels);
+ p_logger->set_log_level_by_value(log_level_value);
+
+ std::stringstream log_messages;
+ log_messages << "Processing Gcode\n";
+ log_messages << "\tSource File Path : " << source_file_path << "\n";
+ log_messages << "\tTarget File File : " << target_file_apth << "\n";
+ log_messages << "\tResolution in MM : " << resolution_mm << "\n";
+ log_messages << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n";
+ log_messages << "\tLog Level : " << log_level_names[logger::get_log_level_for_value(log_level_value)] << "\n";
+ log_messages << "\tShow Progress Updates : " << (show_progress ? "True" : "False") << "\n";
+ std::cout << log_messages.str();
+ arc_welder* p_arc_welder = NULL;
+ if (show_progress)
+ p_arc_welder = new arc_welder(source_file_path, target_file_apth, p_logger, resolution_mm, g90_g91_influences_extruder, 50, on_progress);
+ else
+ p_arc_welder = new arc_welder(source_file_path, target_file_apth, p_logger, resolution_mm, g90_g91_influences_extruder, 50);
+
+ p_arc_welder->process();
+
+ delete p_arc_welder;
+ log_messages.clear();
+ log_messages.str("");
+ log_messages << "Target file at '" << target_file_apth << "' created. Exiting.";
+ p_logger->log(0, INFO, log_messages.str());
+ return 0;
+}
+
+static bool on_progress(double percent_complete, double seconds_elapsed, double seconds_remaining, int gcodes_processed, int current_line, int points_compressed, int arcs_created)
+{
+ std::cout << std::fixed << std::setprecision(1);
+ if (percent_complete < 100)
+ std::cout << percent_complete << "% complete in " << seconds_elapsed << " seconds with " << seconds_remaining << " seconds remaining. Current Line: " << current_line << ", Points Compressed:" << points_compressed << ", ArcsCreated:" << arcs_created << "\r\n";
+ else if (percent_complete >= 100)
+ std::cout << "Processing Completed in " << seconds_elapsed << " seconds.\n\tPoints Compressed:" << points_compressed << "\n\tArcsCreated:" << arcs_created << "\n";
+ std::cout.unsetf(std::ios::floatfield);
+ return true;
+}
\ No newline at end of file
diff --git a/ArcWelderConsole/ArcWelderConsole.h b/ArcWelderConsole/ArcWelderConsole.h
new file mode 100644
index 0000000..35ad744
--- /dev/null
+++ b/ArcWelderConsole/ArcWelderConsole.h
@@ -0,0 +1,26 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Console Application
+//
+// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+static bool on_progress(double percent_complete, double seconds_elapsed, double seconds_remaining, int gcodes_processed, int current_line, int points_compressed, int arcs_created);
\ No newline at end of file
diff --git a/ArcWelderConsole/ArcWelderConsole.vcxproj b/ArcWelderConsole/ArcWelderConsole.vcxproj
new file mode 100644
index 0000000..331e79b
--- /dev/null
+++ b/ArcWelderConsole/ArcWelderConsole.vcxproj
@@ -0,0 +1,161 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {F4910B67-FE16-40EA-9BD5-91017C569B0D}
+ ArcWelderConsole
+ 10.0
+
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+ {1a4dbab1-bb42-4db1-b168-f113784efcef}
+
+
+ {31478bae-104b-4cc3-9876-42fa90cbd5fe}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ArcWelderConsole/ArcWelderConsole.vcxproj.filters b/ArcWelderConsole/ArcWelderConsole.vcxproj.filters
new file mode 100644
index 0000000..af91d71
--- /dev/null
+++ b/ArcWelderConsole/ArcWelderConsole.vcxproj.filters
@@ -0,0 +1,27 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
new file mode 100644
index 0000000..4d1e2fa
--- /dev/null
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
@@ -0,0 +1,42 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Inverse Processor Console Application
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include "inverse_processor.h"
+#include "ArcWelderInverseProcessor.h"
+
+int main(int argc, char* argv[])
+{
+ std::string info = "Arc Welder: Inverse Processor v0.1.rc1.dev0\nConverts G2/G3 commands to G1/G2 commands.\nCopyright(C) 2020 - Brad Hochgesang\n";
+ std::cout << info;
+ TestInverseProcessor(ARC_GYROID_BENCHY_DIFFICULT, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode");
+}
+
+static void TestInverseProcessor(std::string source_path, std::string target_path)
+{
+ inverse_processor processor(source_path, target_path, false, 50);
+ processor.process();
+}
\ No newline at end of file
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h
new file mode 100644
index 0000000..d17f2dd
--- /dev/null
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h
@@ -0,0 +1,52 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Inverse Processor Console Application
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include
+static void TestInverseProcessor(std::string source_path, std::string target_path);
+
+
+static std::string ANTI_STUTTER_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\5x5_cylinder_2000Fn_0.2mm_PLA_MK2.5MMU2_4m.gcode";
+static std::string BENCHY_GCODE = "C:\\Users\\Brad\\Documents\\3DPrinter\\Calibration\\Benchy\\3DBenchy_0.2mm_PLA_MK2.5MMU2.gcode";
+static std::string BENCHY_CURA_RELATIVE_E_NOWIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\3DBenchy_CuraRelative_Gyroid_0.2mm.gcode";
+static std::string BENCHY_GYROID_RELATIVE_E_NOWIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\3DBenchy_0.2mm_gyroid_relative_e_NoWipe.gcode";
+static std::string BENCHY_GYROID_ABSOLUTE_E_NOWIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\3DBenchy_Absolute_Gyroid_0.2mm.gcode";
+static std::string BENCHY_0_5_MM_NO_WIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Benchy_0.5mm_NoWipe.gcode";
+static std::string BENCHY_LAYER_1GCODE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\benchy_l1.gcode";
+static std::string BENCHY_LAYER_1_NO_WIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\benchy_L1_NoWipe.gcode";
+static std::string BENCHY_STACK_RELATIVE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\BenchyStack_Relative.gcode";
+static std::string BENCHY_STACK_ABSOLUTE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\BenchyStack_Absolute.gcode";
+static std::string CAM_RING_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\5milCamRing_0.2mm_PLA_MK2.5MMU2_16m.gcode";
+static std::string FRACTAL = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Mandelbrot.gcode";
+static std::string DIFFICULT_CURVES = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\DifficultCurves.gcode";
+static std::string FACE_SHIELD = "C:\\Users\\Brad\\Documents\\3DPrinter\\corona_virus\\2X_Visor_Frame_0.35mm_PLA_1h25m.gcode";
+static std::string SMALL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\small_test.gcode";
+static std::string SUPER_HUGE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\super_huge_file.gcode";
+static std::string TORTURE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\stereographic_projection_0.2mm_PLA_MK2.5MMU2_2h49m.gcode";
+static std::string ORCHID_POD = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Pla_OrchidPot.gcode";
+static std::string ARC_GYROID_BENCHY_DIFFICULT = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\AS_BenchyArc_Difficult.gcode";
+// Issues
+static std::string ISSUE_MIMUPREFERIDA = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\MIMUPREFERIDA\\TESTSTUTTER.gcode";
+static std::string ISSUE_PRICKLYPEAR = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\PricklyPear\\Barbarian.gcode";
+static std::string ISSUE_PRICKLYPEAR_LAYER_0_114 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\PricklyPear\\Layers0_114.gcode";
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj
new file mode 100644
index 0000000..f525571
--- /dev/null
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj
@@ -0,0 +1,163 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {9C40BB30-5186-4181-94D6-AC8DFE361A5A}
+ ArcWelderInverseProcessor
+ 10.0
+
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ {1a4dbab1-bb42-4db1-b168-f113784efcef}
+
+
+ {31478bae-104b-4cc3-9876-42fa90cbd5fe}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
new file mode 100644
index 0000000..55a31a5
--- /dev/null
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
@@ -0,0 +1,33 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/ArcWelderInverseProcessor/inverse_processor.cpp b/ArcWelderInverseProcessor/inverse_processor.cpp
new file mode 100644
index 0000000..57144b8
--- /dev/null
+++ b/ArcWelderInverseProcessor/inverse_processor.cpp
@@ -0,0 +1,467 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Inverse Processor Console Application
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// This file includes portions from Marlin's motion_control.c file since it is intended to test some firmware modifications.
+// This file was included in the AntiStutter project for convenience, and will not be included within the final version.
+/*
+ motion_control.c - high level interface for issuing motion commands
+ Part of Grbl
+
+ Copyright (c) 2009-2011 Simen Svale Skogsrud
+ Copyright (c) 2011 Sungeun K. Jeon
+ Copyright (c) 2020 Brad Hochgesang
+
+ Grbl is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Grbl is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Grbl. If not, see .
+*/
+
+#include "inverse_processor.h"
+#include "math.h"
+#include
+#include
+#include
+#include
+#include
+
+//#include "Marlin.h"
+//#include "stepper.h"
+//#include "planner.h"
+
+inverse_processor::inverse_processor(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size)
+{
+ source_path_ = source_path;
+ target_path_ = target_path;
+ p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size));
+ // ** Gloabal Variable Definition **
+ // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the
+ // amount of trig funcitons we need to call while printing. For the price of having two globals we
+ // save one trig calc per G2/G3 for both MIN_ARC_SEGMENTS and MIN_MM_PER_ARC_SEGMENT. This is a good trade IMHO.
+#ifdef MIN_ARC_SEGMENTS
+// Determines the radius at which the transition from using MM_PER_ARC_SEGMENT to MIN_ARC_SEGMENTS
+ /*const float*/arc_max_radius_threshold = MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS));
+#endif
+#if defined(MIN_ARC_SEGMENTS) && defined(MIN_MM_PER_ARC_SEGMENT)
+ // Determines the radius at which the transition from using MIN_ARC_SEGMENTS to MIN_MM_PER_ARC_SEGMENT.
+ /*const float*/arc_min_radius_threshold = MIN_MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS));
+#endif
+
+}
+
+gcode_position_args inverse_processor::get_args_(bool g90_g91_influences_extruder, int buffer_size)
+{
+ gcode_position_args args;
+ // Configure gcode_position_args
+ args.g90_influences_extruder = g90_g91_influences_extruder;
+ args.position_buffer_size = buffer_size;
+ args.autodetect_position = true;
+ args.home_x = 0;
+ args.home_x_none = true;
+ args.home_y = 0;
+ args.home_y_none = true;
+ args.home_z = 0;
+ args.home_z_none = true;
+ args.shared_extruder = true;
+ args.zero_based_extruder = true;
+
+
+ args.default_extruder = 0;
+ args.xyz_axis_default_mode = "absolute";
+ args.e_axis_default_mode = "absolute";
+ args.units_default = "millimeters";
+ args.location_detection_commands = std::vector();
+ args.is_bound_ = false;
+ args.is_circular_bed = false;
+ args.x_min = -9999;
+ args.x_max = 9999;
+ args.y_min = -9999;
+ args.y_max = 9999;
+ args.z_min = -9999;
+ args.z_max = 9999;
+ return args;
+}
+
+inverse_processor::~inverse_processor()
+{
+ delete p_source_position_;
+}
+
+void inverse_processor::process()
+{
+ // Create a stringstream we can use for messaging.
+ std::stringstream stream;
+
+ int read_lines_before_clock_check = 5000;
+ //std::cout << "stabilization::process_file - Processing file.\r\n";
+ stream << "Decompressing gcode file.";
+ stream << "Source File: " << source_path_ << "\n";
+ stream << "Target File: " << target_path_ << "\n";
+ std::cout << stream.str();
+ const clock_t start_clock = clock();
+
+ // Create the source file read stream and target write stream
+ std::ifstream gcode_file;
+ std::ofstream output_file;
+ gcode_file.open(source_path_.c_str());
+ output_file.open(target_path_.c_str());
+ std::string line;
+ int lines_with_no_commands = 0;
+ gcode_file.sync_with_stdio(false);
+ output_file.sync_with_stdio(false);
+ gcode_parser parser;
+ int lines_processed = 0;
+ int gcodes_processed = 0;
+ if (gcode_file.is_open())
+ {
+ if (output_file.is_open())
+ {
+ //stream.clear();
+ //stream.str("");
+ //stream << "Opened file for reading. File Size: " << file_size_ << "\n";
+ //std::cout << stream.str();
+ parsed_command cmd;
+ // Communicate every second
+ while (std::getline(gcode_file, line))
+ {
+ lines_processed++;
+
+ cmd.clear();
+ parser.try_parse_gcode(line.c_str(), cmd);
+ bool has_gcode = false;
+ if (cmd.gcode.length() > 0)
+ {
+ has_gcode = true;
+ gcodes_processed++;
+ }
+ else
+ {
+ lines_with_no_commands++;
+ }
+
+ p_source_position_->update(cmd, lines_processed, gcodes_processed, -1);
+
+ if (cmd.command == "G2" || cmd.command == "G3")
+ {
+ position* p_cur_pos = p_source_position_->get_current_position_ptr();
+ position* p_pre_pos = p_source_position_->get_previous_position_ptr();
+ float position[4];
+ position[X_AXIS] = static_cast(p_pre_pos->get_gcode_x());
+ position[Y_AXIS] = static_cast(p_pre_pos->get_gcode_y());
+ position[Z_AXIS] = static_cast(p_pre_pos->get_gcode_z());
+ position[E_AXIS] = static_cast(p_pre_pos->get_current_extruder().get_offset_e());
+ float target[4];
+ target[X_AXIS] = static_cast(p_cur_pos->get_gcode_x());
+ target[Y_AXIS] = static_cast(p_cur_pos->get_gcode_y());
+ target[Z_AXIS] = static_cast(p_cur_pos->get_gcode_z());
+ target[E_AXIS] = static_cast(p_cur_pos->get_current_extruder().get_offset_e());
+ float offset[2];
+ offset[0] = 0.0;
+ offset[1] = 0.0;
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p = cmd.parameters[index];
+ if (p.name == "I")
+ {
+ offset[0] = static_cast(p.double_value);
+ }
+ else if (p.name == "J")
+ {
+ offset[1] = static_cast(p.double_value);
+ }
+ }
+ float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc
+ uint8_t isclockwise = cmd.command == "G2" ? 1 : 0;
+ output_file << mc_arc(position, target, offset, X_AXIS, Y_AXIS, Z_AXIS, static_cast(p_pre_pos->f), radius, isclockwise, 0, p_cur_pos->is_extruder_relative) << "\n";
+ }
+ else
+ {
+ output_file << line << "\n";
+ }
+
+ }
+ output_file.close();
+ }
+ else
+ {
+ std::cout << "Unable to open the output file for writing.\n";
+ }
+ std::cout << "Closing the input file.\n";
+ gcode_file.close();
+ }
+ else
+ {
+ std::cout << "Unable to open the gcode file for processing.\n";
+ }
+
+ const clock_t end_clock = clock();
+ const double total_seconds = (static_cast(end_clock) - static_cast(start_clock)) / CLOCKS_PER_SEC;
+
+ stream.clear();
+ stream.str("");
+ stream << "Completed file processing\r\n";
+ stream << "\tLines Processed : " << lines_processed << "\r\n";
+ stream << "\tTotal Seconds : " << total_seconds << "\r\n";
+ stream << "\tExtra Trig Count : " << trig_calc_count << "\r\n";
+ stream << "\tTotal E Adjustment : " << total_e_adjustment << "\r\n";
+ std::cout << stream.str();
+}
+
+// The arc is approximated by generating a huge number of tiny, linear segments. The length of each
+// segment is configured in settings.mm_per_arc_segment.
+std::string inverse_processor::mc_arc(float* position, float* target, float* offset, uint8_t axis_0, uint8_t axis_1,
+ uint8_t axis_linear, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder, bool output_relative)
+{
+ std::stringstream stream;
+ stream << std::fixed;
+ std::string gcodes;
+
+ // Start Modifications
+ float center_axis0 = position[axis_0] + offset[axis_0];
+ float center_axis1 = position[axis_1] + offset[axis_1];
+ float linear_travel = target[axis_linear] - position[axis_linear];
+ float extruder_travel_total = target[E_AXIS] - position[E_AXIS];
+ float r_axis0 = -offset[axis_0]; // Radius vector from center to current location
+ float r_axis1 = -offset[axis_1];
+ float rt_axis0 = target[axis_0] - center_axis0;
+ float rt_axis1 = target[axis_1] - center_axis1;
+ // 20200419 - Add a variable that will be used to hold the arc segment length
+ float mm_per_arc_segment;
+
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ float angular_travel_total = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
+ if (angular_travel_total < 0) { angular_travel_total += 2 * M_PI; }
+
+#ifdef MIN_ARC_SEGMENTS
+ // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
+ // Do this before converting the angular travel for clockwise rotation
+#ifdef MIN_MM_PER_ARC_SEGMENT
+// 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
+// This prevents a very high number of segments from being generated for curves of a short radius
+ if (radius < arc_min_radius_threshold) mm_per_arc_segment = MIN_MM_PER_ARC_SEGMENT;
+ else
+#endif
+ if (radius < arc_max_radius_threshold) mm_per_arc_segment = radius * ((2.0f * M_PI) / MIN_ARC_SEGMENTS);
+ else mm_per_arc_segment = MM_PER_ARC_SEGMENT;
+#else
+ // 20200418 - FormerLurker - Use the standard segment length
+ mm_per_arc_segment = MM_PER_ARC_SEGMENT;
+#endif
+ if (isclockwise) { angular_travel_total -= 2 * M_PI; }
+
+ //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
+ //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
+ if (position[axis_0] == target[axis_0] && position[axis_1] == target[axis_1] && angular_travel_total == 0)
+ {
+ angular_travel_total += 2 * M_PI;
+ }
+ //end fix G03
+
+ // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
+ // calculating here
+ float millimeters_of_travel_arc = hypot(angular_travel_total * radius, fabs(linear_travel));
+ if (millimeters_of_travel_arc < 0.001) { return ""; }
+ // Calculate the total travel per segment
+ // Calculate the number of arc segments
+ uint16_t segments = static_cast(floor(millimeters_of_travel_arc / mm_per_arc_segment));
+ // Ensure at least one segment
+ if (segments < 1) segments = 1;
+
+ // Calculate theta per segments and linear (z) travel per segment
+ float theta_per_segment = angular_travel_total / segments;
+ float linear_per_segment = linear_travel / (segments);
+
+#ifdef ARC_EXTRUSION_CORRECTION
+ // 20200417 - FormerLurker - The feedrate needs to be adjusted becaue the perimeter of a regular polygon is always
+ // less than that of a circumscribed circle. However, after testing it has been determined that this
+ // value is very small and may not be worth the clock cycles unless the settings are vastlyl different than the
+ // defaults
+
+ // Calculate the individual segment arc and chord length
+ float segment_length_arc = millimeters_of_travel_arc / segments;
+ float segment_length_chord = 2.0f * radius * sin(fabs(theta_per_segment) * 0.5f); // This is a costly calculation..
+ // Determine the correction factor
+ float extrusion_correction_factor = fabs(segment_length_chord / segment_length_arc);
+ // Calculate the corrected extrusion amount per segment
+ float segment_extruder_travel = (extruder_travel_total / segments) * extrusion_correction_factor;
+#else
+ // Calculate the extrusion amount per segment
+ float segment_extruder_travel = extruder_travel_total / (segments);
+#endif
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+
+ // The initial approximation causes irregular movements in some cases, causing back travel to the final segment.
+ // Initialize the linear axis
+ float arc_target[4];
+ arc_target[axis_linear] = position[axis_linear];
+
+ // Initialize the extruder axis
+ arc_target[E_AXIS] = position[E_AXIS];
+
+ // Don't bother calculating cot_T or sin_T if there is only 1 segment. This will speed up arcs that only have
+ // 1 segment.
+ if (segments > 1)
+ {
+ float cos_T;
+ float sin_T;
+ // 20200417 - FormerLurker - Using the small angle approximation causes drift if theta is large.
+ // Use true cos/sin if the angle is large (do we need a definition for this?)
+ if (theta_per_segment < (2.0f * M_PI / 16.0f) && theta_per_segment >(-2.0f * M_PI / 16.0f))
+ {
+ // Avoids cos and sin calculations. However, in my testing this doesn't save much time
+ // since the majority of the cost is adding the segments to the planner. If possible,
+ // I believe it's better to reduce the number of segments as much as possible, even if it
+ // means a bit more overhead in this function. However, for small angles this works fine
+ // and is very fast.
+ cos_T = 1.0f - 0.5f * theta_per_segment * theta_per_segment; // Small angle approximation
+ sin_T = theta_per_segment;
+ }
+ else
+ {
+ // This seems to work even without N_ARC_CORRECTION enabled for all values I tested. It produces
+ // extremely accurate segment endpoints even when an extremely high number of segments are
+ // generated. With 3 decimals of precision on XYZ, this should work for any reasonable settings
+ // without correction.
+ cos_T = cos(theta_per_segment);
+ sin_T = sin(theta_per_segment);
+ }
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ for (i = 1; i < segments; i++) { // Increment (segments-1)
+
+#ifdef N_ARC_CORRECTION
+// 20200417 - FormerLurker - Make N_ARC_CORRECTION optional.
+ if (count < N_ARC_CORRECTION) {
+#endif
+ // Apply vector rotation matrix
+ float x_0 = r_axis0;
+ float y_0 = r_axis1;
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+#ifdef N_ARC_CORRECTION
+ // 20200417 - FormerLurker - Make N_ARC_CORRECTION optional.
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = cos(i * theta_per_segment);
+ sin_Ti = sin(i * theta_per_segment);
+ r_axis0 = -offset[axis_0] * cos_Ti + offset[axis_1] * sin_Ti;
+ r_axis1 = -offset[axis_0] * sin_Ti - offset[axis_1] * cos_Ti;
+ count = 0;
+ }
+#endif
+ // Update arc_target location
+ arc_target[axis_0] = center_axis0 + r_axis0;
+ arc_target[axis_1] = center_axis1 + r_axis1;
+ arc_target[axis_linear] += linear_per_segment;
+ arc_target[E_AXIS] += segment_extruder_travel;
+
+ //********** TODO: MOVE THIS TO ANOTHER FUNCITON AND USE THE ORIGINAL C AS MUCH AS POSSIBLE
+ if (stream.tellp() > 1U)
+ stream << "\n";
+
+ stream << "G1 X" << std::setprecision(3) << arc_target[X_AXIS] << " Y" << arc_target[Y_AXIS];
+ if (output_relative)
+ {
+ stream << std::setprecision(5) << " E" << segment_extruder_travel;
+ }
+ else
+ {
+ stream << std::setprecision(5) << " E" << arc_target[E_AXIS];
+ }
+
+ stream << std::setprecision(0) << " F" << feed_rate;
+ //********** END TODO
+ }
+ }
+
+#ifdef ARC_EXTRUSION_CORRECTION
+ // 20200417 - FormerLurker - adjust the final absolute e coordinate based on our extruder correction
+ target[E_AXIS] = arc_target[E_AXIS] + segment_extruder_travel;
+#endif
+ // Ensure last segment arrives at target location.
+ if (stream.tellp() > 1U)
+ stream << "\n";
+
+ stream << "G1 X" << std::setprecision(3) << target[X_AXIS] << " Y" << target[Y_AXIS];
+ if (output_relative)
+ {
+ stream << std::setprecision(5) << " E" << segment_extruder_travel;
+ }
+ else
+ {
+ stream << std::setprecision(5) << " E" << target[E_AXIS];
+ }
+ stream << std::setprecision(0) << " F" << feed_rate;
+#ifdef ARC_EXTRUSION_CORRECTION
+ // 20200417 - FormerLurker - Hide the e axis corrections from the planner
+ // Is this necessary, and is this the prefered way to accomplish this?
+ //plan_set_e_position(target[E_AXIS]);
+#endif
+ return stream.str();
+
+ // plan_set_acceleration_manager_enabled(acceleration_manager_was_enabled);
+}
+
diff --git a/ArcWelderInverseProcessor/inverse_processor.h b/ArcWelderInverseProcessor/inverse_processor.h
new file mode 100644
index 0000000..761e186
--- /dev/null
+++ b/ArcWelderInverseProcessor/inverse_processor.h
@@ -0,0 +1,64 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Inverse Processor Console Application
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include
+#include "gcode_position.h"
+
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef signed char int8_t;
+#define M_PI 3.14159265358979323846f // pi
+enum AxisEnum { X_AXIS = 0, Y_AXIS= 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 };
+// Arc interpretation settings:
+#define MM_PER_ARC_SEGMENT 1.0f // The maximum length of an arc segment if a full circle of the same radius has more than MIN_ARC_SEGMENTS (if defined)
+#define N_ARC_CORRECTION 25 // The number of interpolated segments that will be generated without a floating point correction
+// 20200417 - FormerLurker - Add Additional Arc Config Values
+#define MIN_ARC_SEGMENTS 32 // The minimum segments in a full circle. If not defined, MM_PER_ARC_SEMGMENT is enforced always
+#define MIN_MM_PER_ARC_SEGMENT 0.25f // the minimum length of an interpolated segment. Must be smaller than MM_PER_ARC_SEGMENT if defined.
+//#define ARC_EXTRUSION_CORRECTION // If defined, we should apply correction to the extrusion length based on the
+ // difference in true arc length. The correctly is extremely small, and may not be worth the cpu cycles
+
+class inverse_processor {
+public:
+ inverse_processor(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size);
+ virtual ~inverse_processor();
+ void process();
+ std::string mc_arc(float* position, float* target, float* offset, uint8_t axis_0, uint8_t axis_1,
+ uint8_t axis_linear, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder, bool output_relative);
+private:
+ gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size);
+ std::string source_path_;
+ std::string target_path_;
+ gcode_position* p_source_position_;
+ float arc_max_radius_threshold;
+ float arc_min_radius_threshold;
+ float total_e_adjustment;
+ int trig_calc_count = 0;
+};
+
+
+
diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp
new file mode 100644
index 0000000..a082bc3
--- /dev/null
+++ b/ArcWelderTest/ArcWelderTest.cpp
@@ -0,0 +1,268 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Test Application
+//
+// This application is only used for ad-hoc testing of the anti-stutter library.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#include "ArcWelderTest.h"
+#include "logger.h"
+#include
+
+int main(int argc, char* argv[])
+{
+ run_tests(argc, argv);
+}
+
+int run_tests(int argc, char* argv[])
+{
+ _CrtMemState state;
+ // This line will take a snapshot
+ // of the memory allocated at this point.
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);
+
+ //std::string filename = argv[1];
+ unsigned int num_runs = 1;
+ _CrtMemCheckpoint(&state);
+
+ auto start = std::chrono::high_resolution_clock::now();
+ for (unsigned int index = 0; index < num_runs; index++)
+ {
+ std::cout << "Processing test run " << index + 1 << " of " << num_runs << ".\r\n";
+ TestAntiStutter(ANTI_STUTTER_TEST);
+ //TestInverseProcessor();
+ //TestCircularBuffer();
+ //TestSegmentedLine();
+ //TestSegmentedArc();
+
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ _CrtMemDumpAllObjectsSince(&state);
+ std::chrono::duration diff = end - start;
+ std::cout << "Tests completed in " << diff.count() << " seconds";
+ //std::cout << "Has Memory Leak = " << has_leak << ".\r\n";
+ // Set the debug-heap flag so that memory leaks are reported when
+ // the process terminates. Then, exit.
+ //printf("Press Any Key to Continue\n");
+ //std::getchar();
+ return 0;
+}
+
+static gcode_position_args get_single_extruder_position_args()
+{
+ gcode_position_args posArgs = gcode_position_args();
+ posArgs.autodetect_position = true;
+ posArgs.home_x = 0;
+ posArgs.home_x_none = true;
+ posArgs.home_y = 0;
+ posArgs.home_y_none = true;
+ posArgs.home_z = 0;
+ posArgs.home_z_none = true;
+ posArgs.shared_extruder = true;
+ posArgs.zero_based_extruder = true;
+ posArgs.set_num_extruders(1);
+ posArgs.retraction_lengths[0] = .8;
+ posArgs.z_lift_heights[0] = .6;
+ posArgs.x_firmware_offsets[0] = 0;
+ posArgs.y_firmware_offsets[0] = 1;
+ posArgs.default_extruder = 0;
+ posArgs.priming_height = 0.4;
+ posArgs.minimum_layer_height = 0.05;
+ posArgs.height_increment = 0.5;
+ posArgs.g90_influences_extruder = false;
+ posArgs.xyz_axis_default_mode = "absolute";
+ posArgs.e_axis_default_mode = "absolute";
+ posArgs.units_default = "millimeters";
+ posArgs.location_detection_commands = std::vector();
+ posArgs.is_bound_ = true;
+ posArgs.is_circular_bed = false;
+ posArgs.snapshot_x_min = 0;
+ posArgs.snapshot_x_max = 250;
+ posArgs.snapshot_y_min = 0;
+ posArgs.snapshot_y_max = 210;
+ posArgs.snapshot_z_min = 0;
+ posArgs.snapshot_z_max = 200;
+ posArgs.x_min = 0;
+ posArgs.x_max = 250;
+ posArgs.y_min = -3;
+ posArgs.y_max = 210;
+ posArgs.z_min = 0;
+ posArgs.z_max = 200;
+ return posArgs;
+}
+
+static gcode_position_args get_5_shared_extruder_position_args()
+{
+ gcode_position_args posArgs = gcode_position_args();
+ posArgs.autodetect_position = true;
+ posArgs.home_x = 0;
+ posArgs.home_x_none = true;
+ posArgs.home_y = 0;
+ posArgs.home_y_none = true;
+ posArgs.home_z = 0;
+ posArgs.home_z_none = true;
+ posArgs.shared_extruder = true;
+ posArgs.zero_based_extruder = true;
+ posArgs.set_num_extruders(5);
+ posArgs.retraction_lengths[0] = .2;
+ posArgs.retraction_lengths[1] = .4;
+ posArgs.retraction_lengths[2] = .6;
+ posArgs.retraction_lengths[3] = .8;
+ posArgs.retraction_lengths[4] = 1;
+ posArgs.z_lift_heights[0] = 1;
+ posArgs.z_lift_heights[1] = .8;
+ posArgs.z_lift_heights[2] = .6;
+ posArgs.z_lift_heights[3] = .4;
+ posArgs.z_lift_heights[4] = .2;
+ posArgs.x_firmware_offsets[0] = 0;
+ posArgs.y_firmware_offsets[0] = 1;
+ posArgs.x_firmware_offsets[1] = 2;
+ posArgs.y_firmware_offsets[1] = 3;
+ posArgs.x_firmware_offsets[2] = 4;
+ posArgs.y_firmware_offsets[2] = 5;
+ posArgs.x_firmware_offsets[3] = 6;
+ posArgs.y_firmware_offsets[3] = 7;
+ posArgs.x_firmware_offsets[4] = 8;
+ posArgs.y_firmware_offsets[4] = 9;
+ posArgs.default_extruder = 0;
+ posArgs.priming_height = 0.4;
+ posArgs.minimum_layer_height = 0.05;
+ posArgs.g90_influences_extruder = false;
+ posArgs.xyz_axis_default_mode = "absolute";
+ posArgs.e_axis_default_mode = "absolute";
+ posArgs.units_default = "millimeters";
+ posArgs.location_detection_commands = std::vector();
+ posArgs.is_bound_ = true;
+ posArgs.is_circular_bed = false;
+ posArgs.snapshot_x_min = 0;
+ posArgs.snapshot_x_max = 250;
+ posArgs.snapshot_y_min = 0;
+ posArgs.snapshot_y_max = 210;
+ posArgs.snapshot_z_min = 0;
+ posArgs.snapshot_z_max = 200;
+ posArgs.x_min = 0;
+ posArgs.x_max = 250;
+ posArgs.y_min = -3;
+ posArgs.y_max = 210;
+ posArgs.z_min = 0;
+ posArgs.z_max = 200;
+ return posArgs;
+}
+
+static gcode_position_args get_5_extruder_position_args()
+{
+ gcode_position_args posArgs = gcode_position_args();
+ posArgs.autodetect_position = true;
+ posArgs.home_x = 0;
+ posArgs.home_x_none = true;
+ posArgs.home_y = 0;
+ posArgs.home_y_none = true;
+ posArgs.home_z = 0;
+ posArgs.home_z_none = true;
+ posArgs.shared_extruder = false;
+ posArgs.zero_based_extruder = true;
+ posArgs.set_num_extruders(5);
+ posArgs.retraction_lengths[0] = .2;
+ posArgs.retraction_lengths[1] = .4;
+ posArgs.retraction_lengths[2] = .6;
+ posArgs.retraction_lengths[3] = .8;
+ posArgs.retraction_lengths[4] = 1;
+ posArgs.z_lift_heights[0] = 1;
+ posArgs.z_lift_heights[1] = .8;
+ posArgs.z_lift_heights[2] = .6;
+ posArgs.z_lift_heights[3] = .4;
+ posArgs.z_lift_heights[4] = .2;
+ posArgs.x_firmware_offsets[0] = 0;
+ posArgs.y_firmware_offsets[0] = 0;
+ posArgs.x_firmware_offsets[1] = 5;
+ posArgs.y_firmware_offsets[1] = 0;
+ posArgs.x_firmware_offsets[2] = 0;
+ posArgs.y_firmware_offsets[2] = 0;
+ posArgs.x_firmware_offsets[3] = 0;
+ posArgs.y_firmware_offsets[3] = 0;
+ posArgs.x_firmware_offsets[4] = 0;
+ posArgs.y_firmware_offsets[4] = 0;
+ posArgs.default_extruder = 0;
+ posArgs.priming_height = 0.4;
+ posArgs.minimum_layer_height = 0.05;
+ posArgs.g90_influences_extruder = false;
+ posArgs.xyz_axis_default_mode = "absolute";
+ posArgs.e_axis_default_mode = "absolute";
+ posArgs.units_default = "millimeters";
+ posArgs.location_detection_commands = std::vector();
+ posArgs.is_bound_ = true;
+ posArgs.is_circular_bed = false;
+ posArgs.snapshot_x_min = 0;
+ posArgs.snapshot_x_max = 250;
+ posArgs.snapshot_y_min = 0;
+ posArgs.snapshot_y_max = 210;
+ posArgs.snapshot_z_min = 0;
+ posArgs.snapshot_z_max = 200;
+ posArgs.x_min = 0;
+ posArgs.x_max = 250;
+ posArgs.y_min = -3;
+ posArgs.y_max = 210;
+ posArgs.z_min = 0;
+ posArgs.z_max = 200;
+ return posArgs;
+}
+
+static void TestAntiStutter(std::string filePath)
+{
+ double max_resolution = 0.05;
+ std::vector logger_names;
+ logger_names.push_back("arc_welder.gcode_conversion");
+ std::vector logger_levels;
+ //logger_levels.push_back(log_levels::DEBUG);
+ logger_levels.push_back(log_levels::INFO);
+ logger* p_logger = new logger(logger_names, logger_levels);
+ p_logger->set_log_level(INFO);
+ //arc_welder arc_welder_obj(BENCHY_0_5_MM_NO_WIPE, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, static_cast(on_progress));
+ arc_welder arc_welder_obj(ISSUE_PRICKLYPEAR, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50);
+ //BENCHY_LAYER_1GCODE
+ //SMALL_TEST
+ //FACE_SHIELD
+ //BENCHY_LAYER_1_NO_WIPE
+ //BENCHY_0_5_MM_NO_WIPE
+ //BENCHY_CURA_RELATIVE_E_NOWIPE
+ //BENCHY_CURA_ABSOLUTE_E_NOWIPE
+ //BENCHY_GYROID_RELATIVE_E_NOWIPE
+ //BENCHY_STACK_RELATIVE
+ //BENCHY_STACK_ABSOLUTE
+ //FRACTAL
+ //SUPER_HUGE_TEST
+ //TORTURE_TEST
+ //ORCHID_POD
+ //DIFFICULT_CURVES
+ //ISSUE_PRICKLYPEAR_LAYER_0_114
+ arc_welder_obj.process();
+ p_logger->log(0, INFO, "Processing Complete.");
+ delete p_logger;
+}
+
+static bool on_progress(double percentComplete, double secondsElapsed, double estimatedSecondsRemaining, int gcodes_processed, int currentLine, int points_compressed, int arcs_created)
+{
+ std::cout << percentComplete << "% complete in " << secondsElapsed << " seconds with " << estimatedSecondsRemaining << " seconds remaining. Current Line: " << currentLine << ", Points Compressed:" << points_compressed << ", ArcsCreated:" << arcs_created << "\r\n";
+ return true;
+}
diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h
new file mode 100644
index 0000000..4cda18a
--- /dev/null
+++ b/ArcWelderTest/ArcWelderTest.h
@@ -0,0 +1,72 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Test Application
+//
+// This application is only used for ad-hoc testing of the anti-stutter library.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "gcode_position.h"
+#include "gcode_parser.h"
+#include
+#include "arc_welder.h"
+#include "array_list.h"
+#include "logger.h"
+#include
+
+int run_tests(int argc, char* argv[]);
+static gcode_position_args get_single_extruder_position_args();
+static gcode_position_args get_5_shared_extruder_position_args();
+static gcode_position_args get_5_extruder_position_args();
+static void TestAntiStutter(std::string filePath);
+static bool on_progress(double percentComplete, double secondsElapsed, double estimatedSecondsRemaining, int gcodes_processed, int currentLine, int points_compressed, int arcs_created);
+
+
+static std::string ANTI_STUTTER_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\5x5_cylinder_2000Fn_0.2mm_PLA_MK2.5MMU2_4m.gcode";
+static std::string BENCHY_GCODE = "C:\\Users\\Brad\\Documents\\3DPrinter\\Calibration\\Benchy\\3DBenchy_0.2mm_PLA_MK2.5MMU2.gcode";
+static std::string BENCHY_CURA_RELATIVE_E_NOWIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\3DBenchy_CuraRelative_Gyroid_0.2mm.gcode";
+static std::string BENCHY_GYROID_RELATIVE_E_NOWIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\3DBenchy_0.2mm_gyroid_relative_e_NoWipe.gcode";
+static std::string BENCHY_GYROID_ABSOLUTE_E_NOWIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\3DBenchy_Absolute_Gyroid_0.2mm.gcode";
+static std::string BENCHY_0_5_MM_NO_WIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Benchy_0.5mm_NoWipe.gcode";
+static std::string BENCHY_LAYER_1GCODE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\benchy_l1.gcode";
+static std::string BENCHY_LAYER_1_NO_WIPE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\benchy_L1_NoWipe.gcode";
+static std::string BENCHY_STACK_RELATIVE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\BenchyStack_Relative.gcode";
+static std::string BENCHY_STACK_ABSOLUTE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\BenchyStack_Absolute.gcode";
+static std::string CAM_RING_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\5milCamRing_0.2mm_PLA_MK2.5MMU2_16m.gcode";
+static std::string FRACTAL = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Mandelbrot.gcode";
+static std::string DIFFICULT_CURVES = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\DifficultCurves.gcode";
+static std::string FACE_SHIELD = "C:\\Users\\Brad\\Documents\\3DPrinter\\corona_virus\\2X_Visor_Frame_0.35mm_PLA_1h25m.gcode";
+static std::string SMALL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\small_test.gcode";
+static std::string SUPER_HUGE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\super_huge_file.gcode";
+static std::string TORTURE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\stereographic_projection_0.2mm_PLA_MK2.5MMU2_2h49m.gcode";
+static std::string ORCHID_POD = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Pla_OrchidPot.gcode";
+static std::string ARC_GYROID_BENCHY_DIFFICULT = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\AS_BenchyArc_Difficult.gcode";
+// Issues
+static std::string ISSUE_MIMUPREFERIDA = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\MIMUPREFERIDA\\TESTSTUTTER.gcode";
+static std::string ISSUE_PRICKLYPEAR = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\PricklyPear\\Barbarian.gcode";
+static std::string ISSUE_PRICKLYPEAR_LAYER_0_114 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\PricklyPear\\Layers0_114.gcode";
diff --git a/ArcWelderTest/ArcWelderTest.vcxproj b/ArcWelderTest/ArcWelderTest.vcxproj
new file mode 100644
index 0000000..553380a
--- /dev/null
+++ b/ArcWelderTest/ArcWelderTest.vcxproj
@@ -0,0 +1,161 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {18D7E538-6ACE-44E4-B83E-31C3E44D4227}
+ ArcWelderTest
+ 10.0
+
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ true
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+ false
+ $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+ {1a4dbab1-bb42-4db1-b168-f113784efcef}
+
+
+ {31478bae-104b-4cc3-9876-42fa90cbd5fe}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ArcWelderTest/ArcWelderTest.vcxproj.filters b/ArcWelderTest/ArcWelderTest.vcxproj.filters
new file mode 100644
index 0000000..9ecb598
--- /dev/null
+++ b/ArcWelderTest/ArcWelderTest.vcxproj.filters
@@ -0,0 +1,27 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/GcodeProcessorLib/GcodeProcessorLib.vcxproj b/GcodeProcessorLib/GcodeProcessorLib.vcxproj
new file mode 100644
index 0000000..9d8c738
--- /dev/null
+++ b/GcodeProcessorLib/GcodeProcessorLib.vcxproj
@@ -0,0 +1,169 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {31478BAE-104B-4CC3-9876-42FA90CBD5FE}
+ GcodeProcessorLib
+ 10.0
+
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+ Level3
+ true
+ _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters b/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters
new file mode 100644
index 0000000..3981297
--- /dev/null
+++ b/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters
@@ -0,0 +1,87 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/GcodeProcessorLib/array_list.cpp b/GcodeProcessorLib/array_list.cpp
new file mode 100644
index 0000000..b2e46ea
--- /dev/null
+++ b/GcodeProcessorLib/array_list.cpp
@@ -0,0 +1,22 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#include "array_list.h"
\ No newline at end of file
diff --git a/GcodeProcessorLib/array_list.h b/GcodeProcessorLib/array_list.h
new file mode 100644
index 0000000..a5ce323
--- /dev/null
+++ b/GcodeProcessorLib/array_list.h
@@ -0,0 +1,163 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include
+template
+class array_list
+{
+public:
+ array_list()
+ {
+ auto_grow_ = true;
+ max_size_ = 50;
+ front_index_ = 0;
+ count_ = 0;
+ items_ = new T[max_size_];
+ }
+ array_list(int max_size)
+ {
+ auto_grow_ = false;
+ max_size_ = max_size;
+ front_index_ = 0;
+ count_ = 0;
+ items_ = new T[max_size];
+ }
+ virtual ~array_list() {
+ delete[] items_;
+ }
+ void resize(int max_size)
+ {
+ T* new_items = new T[max_size];
+ for (int index = 0; index < count_; index++)
+ {
+ new_items[index] = items_[(front_index_ + index + max_size_) % max_size_];
+ }
+ front_index_ = 0;
+ delete[] items_;
+ items_ = new_items;
+ max_size_ = max_size;
+ }
+ void push_front(T object)
+ {
+ if (count_ == max_size_)
+ {
+ if (auto_grow_)
+ {
+ resize(max_size_ * 2);
+ }
+ else {
+ throw std::exception();
+ }
+ }
+ front_index_ = (front_index_ - 1 + max_size_) % max_size_;
+ count_++;
+ items_[front_index_] = object;
+ }
+ void push_back(T object)
+ {
+ if (count_ == max_size_)
+ {
+ if (auto_grow_)
+ {
+ resize(max_size_ * 2);
+ }
+ else {
+ throw std::exception();
+ }
+ }
+ items_[(front_index_ + count_ + max_size_) % max_size_] = object;
+ count_++;
+ }
+ T pop_front()
+ {
+ if (count_ == 0)
+ {
+ throw std::exception();
+ }
+
+ int prev_start = front_index_;
+ front_index_ = (front_index_ + 1 + max_size_) % max_size_;
+ count_--;
+ return items_[prev_start];
+ }
+
+ T pop_back()
+ {
+ if (count_ == 0)
+ {
+ throw std::exception();
+ }
+
+ return items_[--count_];
+ }
+ T& operator[](int index)
+ {
+ return items_[(front_index_ + index + max_size_) % max_size_];
+ }
+ const T& operator[] (const int index) const
+ {
+ return items_[(front_index_ + index + max_size_) % max_size_];
+ }
+
+ const T get(int index)
+ {
+ return items_[(front_index_ + index + max_size_) % max_size_];
+ }
+
+ int count()
+ {
+ return count_;
+
+ }
+ int get_max_size()
+ {
+ return max_size_;
+ }
+ void clear()
+ {
+ count_ = 0;
+ front_index_ = 0;
+ }
+ void copy(const array_list& source)
+ {
+ if (max_size_ < source.max_size_)
+ {
+ resize(source.max_size_);
+ }
+ clear();
+ for (int index = 0; index < source.count_; index++)
+ {
+ items_[index] = source[index];
+ }
+ front_index_ = source.front_index_;
+ count_ = source.count_;
+ }
+
+protected:
+ T* items_;
+ int max_size_;
+ int front_index_;
+ int count_;
+ bool auto_grow_;
+};
\ No newline at end of file
diff --git a/GcodeProcessorLib/circular_buffer.cpp b/GcodeProcessorLib/circular_buffer.cpp
new file mode 100644
index 0000000..c17c240
--- /dev/null
+++ b/GcodeProcessorLib/circular_buffer.cpp
@@ -0,0 +1,22 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#include "circular_buffer.h"
\ No newline at end of file
diff --git a/GcodeProcessorLib/circular_buffer.h b/GcodeProcessorLib/circular_buffer.h
new file mode 100644
index 0000000..1658b78
--- /dev/null
+++ b/GcodeProcessorLib/circular_buffer.h
@@ -0,0 +1,117 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include
+template
+class circular_buffer
+{
+public:
+ circular_buffer()
+ {
+ max_size_ = 50;
+ front_index_ = 0;
+ count_ = 0;
+ items_ = new T[max_size_];
+ }
+ circular_buffer(int max_size)
+ {
+ max_size_ = max_size;
+ front_index_ = 0;
+ count_ = 0;
+ items_ = new T[max_size];
+ }
+ virtual ~circular_buffer() {
+ delete[] items_;
+ }
+ void resize(int max_size)
+ {
+ T* new_items = new T[max_size];
+ int count = count_;
+ for (int index = 0; index < count_; index++)
+ {
+ new_items[index] = items_[(front_index_ + index + max_size_) % max_size_];
+ }
+ front_index_ = 0;
+ delete[] items_;
+ items_ = new_items;
+ max_size_ = max_size;
+ }
+ void push_front(T object)
+ {
+ front_index_ = (front_index_ - 1 + max_size_) % max_size_;
+ count_++;
+ items_[front_index_] = object;
+ }
+ T pop_front()
+ {
+ if (count_ == 0)
+ {
+ throw std::exception();
+ }
+
+ int prev_start = front_index_;
+ front_index_ = (front_index_ + 1 + max_size_) % max_size_;
+ count_--;
+ return items_[prev_start];
+ }
+
+ T get(int index)
+ {
+ return items_[(front_index_ + index + max_size_) % max_size_];
+ }
+
+ int count()
+ {
+ return count_;
+
+ }
+ int get_max_size()
+ {
+ return max_size_;
+ }
+ void clear()
+ {
+ count_ = 0;
+ front_index_ = 0;
+ }
+ void copy(const circular_buffer& source)
+ {
+ if (max_size_ < source.max_size_)
+ {
+ resize(source.max_size_);
+ }
+ clear();
+ for (int index = 0; index < source.count_; index++)
+ {
+ items_[index] = source[index];
+ }
+ front_index_ = source.front_index_;
+ count_ = source.count_;
+
+ }
+
+protected:
+ T* items_;
+ int max_size_;
+ int front_index_;
+ int count_;
+};
\ No newline at end of file
diff --git a/GcodeProcessorLib/extruder.cpp b/GcodeProcessorLib/extruder.cpp
new file mode 100644
index 0000000..b113c97
--- /dev/null
+++ b/GcodeProcessorLib/extruder.cpp
@@ -0,0 +1,53 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "extruder.h"
+#include
+
+extruder::extruder()
+{
+ x_firmware_offset = 0;
+ y_firmware_offset = 0;
+ z_firmware_offset = 0;
+ e = 0;
+ e_offset = 0;
+ e_relative = 0;
+ extrusion_length = 0;
+ extrusion_length_total = 0;
+ retraction_length = 0;
+ deretraction_length = 0;
+ is_extruding_start = false;
+ is_extruding = false;
+ is_primed = false;
+ is_retracting_start = false;
+ is_retracting = false;
+ is_retracted = false;
+ is_partially_retracted = false;
+ is_deretracting_start = false;
+ is_deretracting = false;
+ is_deretracted = false;
+}
+
+double extruder::get_offset_e() const
+{
+ return e - e_offset;
+}
diff --git a/GcodeProcessorLib/extruder.h b/GcodeProcessorLib/extruder.h
new file mode 100644
index 0000000..8fcda2b
--- /dev/null
+++ b/GcodeProcessorLib/extruder.h
@@ -0,0 +1,49 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include
+struct extruder
+{
+ extruder();
+ double x_firmware_offset;
+ double y_firmware_offset;
+ double z_firmware_offset;
+ double e;
+ double e_offset;
+ double e_relative;
+ double extrusion_length;
+ double extrusion_length_total;
+ double retraction_length;
+ double deretraction_length;
+ bool is_extruding_start;
+ bool is_extruding;
+ bool is_primed;
+ bool is_retracting_start;
+ bool is_retracting;
+ bool is_retracted;
+ bool is_partially_retracted;
+ bool is_deretracting_start;
+ bool is_deretracting;
+ bool is_deretracted;
+ double get_offset_e() const;
+};
+
diff --git a/GcodeProcessorLib/gcode_comment_processor.cpp b/GcodeProcessorLib/gcode_comment_processor.cpp
new file mode 100644
index 0000000..e9acf3c
--- /dev/null
+++ b/GcodeProcessorLib/gcode_comment_processor.cpp
@@ -0,0 +1,310 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#include "gcode_comment_processor.h"
+
+gcode_comment_processor::gcode_comment_processor()
+{
+ current_section_ = section_type_no_section;
+ processing_type_ = comment_process_type_unknown;
+}
+
+gcode_comment_processor::~gcode_comment_processor()
+{
+}
+
+comment_process_type gcode_comment_processor::get_comment_process_type()
+{
+ return processing_type_;
+}
+
+void gcode_comment_processor::update(position& pos)
+{
+ if (processing_type_ == comment_process_type_off)
+ return;
+
+ if (current_section_ != section_type_no_section)
+ {
+ update_feature_from_section(pos);
+ return;
+ }
+
+ if (processing_type_ == comment_process_type_unknown || processing_type_ == comment_process_type_slic3r_pe)
+ {
+ if (update_feature_for_slic3r_pe_comment(pos, pos.command.comment))
+ processing_type_ = comment_process_type_slic3r_pe;
+ }
+
+}
+
+bool gcode_comment_processor::update_feature_for_slic3r_pe_comment(position& pos, std::string &comment) const
+{
+ if (comment == "perimeter" || comment == "move to first perimeter point")
+ {
+ pos.feature_type_tag = feature_type_unknown_perimeter_feature;
+ return true;
+ }
+ if (comment == "infill" || comment == "move to first infill point")
+ {
+ pos.feature_type_tag = feature_type_infill_feature;
+ return true;
+ }
+ if (comment == "infill(bridge)" || comment == "move to first infill(bridge) point")
+ {
+ pos.feature_type_tag = feature_type_bridge_feature;
+ return true;
+ }
+ if (comment == "skirt" || comment == "move to first skirt point")
+ {
+ pos.feature_type_tag = feature_type_skirt_feature;
+ return true;
+ }
+ return false;
+}
+
+void gcode_comment_processor::update_feature_from_section(position& pos) const
+{
+ if (processing_type_ == comment_process_type_off || current_section_ == section_type_no_section)
+ return;
+
+ switch (current_section_)
+ {
+ case(section_type_outer_perimeter_section):
+ pos.feature_type_tag = feature_type_outer_perimeter_feature;
+ break;
+ case(section_type_inner_perimeter_section):
+ pos.feature_type_tag = feature_type_inner_perimeter_feature;
+ break;
+ case(section_type_skirt_section):
+ pos.feature_type_tag = feature_type_skirt_feature;
+ break;
+ case(section_type_solid_infill_section):
+ pos.feature_type_tag = feature_type_solid_infill_feature;
+ break;
+ case(section_type_ooze_shield_section):
+ pos.feature_type_tag = feature_type_ooze_shield_feature;
+ break;
+ case(section_type_infill_section):
+ pos.feature_type_tag = feature_type_infill_feature;
+ break;
+ case(section_type_prime_pillar_section):
+ pos.feature_type_tag = feature_type_prime_pillar_feature;
+ break;
+ case(section_type_gap_fill_section):
+ pos.feature_type_tag = feature_type_gap_fill_feature;
+ break;
+ case(section_type_no_section):
+ // Do Nothing
+ break;
+ }
+}
+
+void gcode_comment_processor::update(std::string & comment)
+{
+ switch(processing_type_)
+ {
+ case comment_process_type_off:
+ break;
+ case comment_process_type_unknown:
+ update_unknown_section(comment);
+ break;
+ case comment_process_type_cura:
+ update_cura_section(comment);
+ break;
+ case comment_process_type_slic3r_pe:
+ update_slic3r_pe_section(comment);
+ break;
+ case comment_process_type_simplify_3d:
+ update_simplify_3d_section(comment);
+ break;
+ }
+}
+
+void gcode_comment_processor::update_unknown_section(std::string & comment)
+{
+ if (comment.length() == 0)
+ return;
+
+ if (update_cura_section(comment))
+ {
+ processing_type_ = comment_process_type_cura;
+ return;
+ }
+
+ if (update_simplify_3d_section(comment))
+ {
+ processing_type_ = comment_process_type_simplify_3d;
+ return;
+ }
+ if(update_slic3r_pe_section(comment))
+ {
+ processing_type_ = comment_process_type_slic3r_pe;
+ return;
+ }
+}
+
+bool gcode_comment_processor::update_cura_section(std::string &comment)
+{
+ if (comment == "TYPE:WALL-OUTER")
+ {
+ current_section_ = section_type_outer_perimeter_section;
+ return true;
+ }
+ else if (comment == "TYPE:WALL-INNER")
+ {
+ current_section_ = section_type_inner_perimeter_section;
+ return true;
+ }
+ if (comment == "TYPE:FILL")
+ {
+ current_section_ = section_type_infill_section;
+ return true;
+ }
+ if (comment == "TYPE:SKIN")
+ {
+ current_section_ = section_type_solid_infill_section;
+ return true;
+ }
+ if (comment.rfind("LAYER:", 0) != std::string::npos || comment.rfind(";MESH:NONMESH", 0) != std::string::npos)
+ {
+ current_section_ = section_type_no_section;
+ return false;
+ }
+ if (comment == "TYPE:SKIRT")
+ {
+ current_section_ = section_type_skirt_section;
+ return true;
+ }
+ return false;
+}
+
+bool gcode_comment_processor::update_simplify_3d_section(std::string &comment)
+{
+ // Apparently simplify 3d added the word 'feature' to the their feature comments
+ // at some point to make my life more difficult :P
+ if (comment.rfind("feature", 0) != std::string::npos)
+ {
+ if (comment == "feature outer perimeter")
+ {
+ current_section_ = section_type_outer_perimeter_section;
+ return true;
+ }
+ if (comment == "feature inner perimeter")
+ {
+ current_section_ = section_type_inner_perimeter_section;
+ return true;
+ }
+ if (comment == "feature infill")
+ {
+ current_section_ = section_type_infill_section;
+ return true;
+ }
+ if (comment == "feature solid layer")
+ {
+ current_section_ = section_type_solid_infill_section;
+ return true;
+ }
+ if (comment == "feature skirt")
+ {
+ current_section_ = section_type_skirt_section;
+ return true;
+ }
+ if (comment == "feature ooze shield")
+ {
+ current_section_ = section_type_ooze_shield_section;
+ return true;
+ }
+ if (comment == "feature prime pillar")
+ {
+ current_section_ = section_type_prime_pillar_section;
+ return true;
+ }
+ if (comment == "feature gap fill")
+ {
+ current_section_ = section_type_gap_fill_section;
+ return true;
+ }
+ }
+ else
+ {
+ if (comment == "outer perimeter")
+ {
+ current_section_ = section_type_outer_perimeter_section;
+ return true;
+ }
+ if (comment == "inner perimeter")
+ {
+ current_section_ = section_type_inner_perimeter_section;
+ return true;
+ }
+ if (comment == "infill")
+ {
+ current_section_ = section_type_infill_section;
+ return true;
+ }
+ if (comment == "solid layer")
+ {
+ current_section_ = section_type_solid_infill_section;
+ return true;
+ }
+ if (comment == "skirt")
+ {
+ current_section_ = section_type_skirt_section;
+ return true;
+ }
+ if (comment == "ooze shield")
+ {
+ current_section_ = section_type_ooze_shield_section;
+ return true;
+ }
+
+ if (comment == "prime pillar")
+ {
+ current_section_ = section_type_prime_pillar_section;
+ return true;
+ }
+
+ if (comment == "gap fill")
+ {
+ current_section_ = section_type_gap_fill_section;
+ return true;
+ }
+ }
+
+
+ return false;
+}
+
+bool gcode_comment_processor::update_slic3r_pe_section(std::string &comment)
+{
+ if (comment == "CP TOOLCHANGE WIPE")
+ {
+ current_section_ = section_type_prime_pillar_section;
+ return true;
+ }
+ if (comment == "CP TOOLCHANGE END")
+ {
+ current_section_ = section_type_no_section;
+ return true;
+ }
+ return false;
+}
+
diff --git a/GcodeProcessorLib/gcode_comment_processor.h b/GcodeProcessorLib/gcode_comment_processor.h
new file mode 100644
index 0000000..49ad394
--- /dev/null
+++ b/GcodeProcessorLib/gcode_comment_processor.h
@@ -0,0 +1,90 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+#include "position.h"
+#define NUM_FEATURE_TYPES 11
+static const std::string feature_type_name[NUM_FEATURE_TYPES] = {
+ "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"
+};
+enum feature_type
+{
+ feature_type_unknown_feature,
+ feature_type_bridge_feature,
+ feature_type_outer_perimeter_feature,
+ feature_type_unknown_perimeter_feature,
+ feature_type_inner_perimeter_feature,
+ feature_type_skirt_feature,
+ feature_type_gap_fill_feature,
+ feature_type_solid_infill_feature,
+ feature_type_ooze_shield_feature,
+ feature_type_infill_feature,
+ feature_type_prime_pillar_feature
+};
+enum comment_process_type
+{
+ comment_process_type_off,
+ comment_process_type_unknown,
+ comment_process_type_slic3r_pe,
+ comment_process_type_cura,
+ comment_process_type_simplify_3d
+};
+// used for marking slicer sections for cura and simplify 3d
+enum section_type
+{
+ section_type_no_section,
+ section_type_outer_perimeter_section,
+ section_type_inner_perimeter_section,
+ section_type_infill_section,
+ section_type_gap_fill_section,
+ section_type_skirt_section,
+ section_type_solid_infill_section,
+ section_type_ooze_shield_section,
+ section_type_prime_pillar_section
+};
+
+class gcode_comment_processor
+{
+
+public:
+
+ gcode_comment_processor();
+ ~gcode_comment_processor();
+ void update(position& pos);
+ void update(std::string & comment);
+ comment_process_type get_comment_process_type();
+
+private:
+ section_type current_section_;
+ comment_process_type processing_type_;
+ void update_feature_from_section(position& pos) const;
+ bool update_feature_from_section_from_section(position& pos) const;
+ bool update_feature_from_section_for_cura(position& pos) const;
+ bool update_feature_from_section_for_simplify_3d(position& pos) const;
+ bool update_feature_from_section_for_slice3r_pe(position& pos) const;
+ void update_feature_for_unknown_slicer_comment(position& pos, std::string &comment);
+ bool update_feature_for_slic3r_pe_comment(position& pos, std::string &comment) const;
+ void update_unknown_section(std::string & comment);
+ bool update_cura_section(std::string &comment);
+ bool update_simplify_3d_section(std::string &comment);
+ bool update_slic3r_pe_section(std::string &comment);
+};
+
diff --git a/GcodeProcessorLib/gcode_parser.cpp b/GcodeProcessorLib/gcode_parser.cpp
new file mode 100644
index 0000000..a4e7d0a
--- /dev/null
+++ b/GcodeProcessorLib/gcode_parser.cpp
@@ -0,0 +1,676 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "gcode_parser.h"
+#include "utilities.h"
+#include
+#include
+gcode_parser::gcode_parser()
+{
+ // doesn't work in the ancient version of c++ I am forced to use :(
+ // or at least I don't know how to us a newer one with python 2.7
+ // help...
+ /*
+ std::vector text_only_function_names = { "M117" }; // "M117" is an example of a command that would work here.
+
+ std::vector parsable_command_names = {
+ "G0","G1","G2","G3","G10","G11","G20","G21","G28","G29","G80","G90","G91","G92","M82","M83","M104","M105","M106","M109","M114","M116","M140","M141","M190","M191","M207","M208","M240","M400","T"
+ };
+ */
+ // Have to resort to barbarity.
+ // Text only function names
+ std::vector text_only_function_names;
+ text_only_function_names.push_back(("M117"));
+ // parsable_command_names
+ std::vector parsable_command_names;
+ parsable_command_names.push_back("G0");
+ parsable_command_names.push_back("G1");
+ parsable_command_names.push_back("G2");
+ parsable_command_names.push_back("G3");
+ parsable_command_names.push_back("G10");
+ parsable_command_names.push_back("G11");
+ parsable_command_names.push_back("G20");
+ parsable_command_names.push_back("G21");
+ parsable_command_names.push_back("G28");
+ parsable_command_names.push_back("G29");
+ parsable_command_names.push_back("G80");
+ parsable_command_names.push_back("G90");
+ parsable_command_names.push_back("G91");
+ parsable_command_names.push_back("G92");
+ parsable_command_names.push_back("M82");
+ parsable_command_names.push_back("M83");
+ parsable_command_names.push_back("M104");
+ parsable_command_names.push_back("M105");
+ parsable_command_names.push_back("M106");
+ parsable_command_names.push_back("M109");
+ parsable_command_names.push_back("M114");
+ parsable_command_names.push_back("M116");
+ parsable_command_names.push_back("M140");
+ parsable_command_names.push_back("M141");
+ parsable_command_names.push_back("M190");
+ parsable_command_names.push_back("M191");
+ parsable_command_names.push_back("M207");
+ parsable_command_names.push_back("M208");
+ parsable_command_names.push_back("M218");
+ parsable_command_names.push_back("M240");
+ parsable_command_names.push_back("M400");
+ parsable_command_names.push_back("M563");
+ parsable_command_names.push_back("T");
+ parsable_command_names.push_back("@OCTOLAPSE");
+
+ for (unsigned int index = 0; index < text_only_function_names.size(); index++)
+ {
+ std::string functionName = text_only_function_names[index];
+ text_only_functions_.insert(functionName);
+ }
+
+ for (unsigned int index = 0; index < parsable_command_names.size(); index++)
+ {
+ std::string commandName = parsable_command_names[index];
+ parsable_commands_.insert(commandName);
+ }
+
+}
+
+gcode_parser::gcode_parser(const gcode_parser &source)
+{
+ // Private copy constructor - you can't copy this class
+}
+
+gcode_parser::~gcode_parser()
+{
+ text_only_functions_.clear();
+ parsable_commands_.clear();
+}
+
+parsed_command gcode_parser::parse_gcode(const char * gcode)
+{
+
+ parsed_command p_cmd;
+ try_parse_gcode(gcode, p_cmd);
+ return p_cmd;
+}
+
+// Superfast gcode parser - v2
+bool gcode_parser::try_parse_gcode(const char * gcode, parsed_command & command)
+{
+ // Create a command
+ char * p_gcode = const_cast(gcode);
+ char * p = const_cast(gcode);
+ command.is_empty = true;
+ command.is_known_command = try_extract_gcode_command(&p, &(command.command));
+ if (!command.is_known_command)
+ {
+ while (true)
+ {
+ char c = *p_gcode;
+ if (c == '\0' || c == ';' || c == ' ' || c == '\t')
+ break;
+ else if (c > 31)
+ {
+ command.is_empty = false;
+ break;
+ }
+ p_gcode++;
+ }
+ command.command = "";
+ }
+ else
+ command.is_empty = false;
+
+ bool has_seen_character = false;
+ while (true)
+ {
+ char cur_char = *p_gcode;
+ if (cur_char == '\0' || cur_char == ';')
+ break;
+ else if (cur_char > 32 || (cur_char == ' ' && has_seen_character))
+ {
+ if (cur_char >= 'a' && cur_char <= 'z')
+ command.gcode.push_back(cur_char - 32);
+ else
+ command.gcode.push_back(cur_char);
+ has_seen_character = true;
+ }
+ p_gcode++;
+ }
+ command.gcode = utilities::rtrim(command.gcode);
+
+ if (command.is_known_command)
+ {
+
+ if (parsable_commands_.find(command.command) == parsable_commands_.end())
+ {
+ return true;
+ }
+ if (command.command.length() > 0 && command.command == "@OCTOLAPSE")
+ {
+
+ parsed_command_parameter octolapse_parameter;
+
+ if (!try_extract_octolapse_parameter(&p, &octolapse_parameter))
+ {
+ return true;
+ }
+ command.parameters.push_back(octolapse_parameter);
+ // Extract any additional parameters the old way
+ while (true)
+ {
+ //std::cout << "GcodeParser.try_parse_gcode - Trying to extract parameters.\r\n";
+ parsed_command_parameter param;
+ if (try_extract_parameter(&p, ¶m))
+ command.parameters.push_back(param);
+ else
+ {
+ //std::cout << "GcodeParser.try_parse_gcode - No parameters found.\r\n";
+ break;
+ }
+ }
+
+ }
+ else if (
+ text_only_functions_.find(command.command) != text_only_functions_.end() ||
+ (
+ command.command.length() > 0 && command.command[0] == '@'
+ )
+ ){
+ //std::cout << "GcodeParser.try_parse_gcode - Text only parameter found.\r\n";
+ parsed_command_parameter text_command;
+ if (!try_extract_text_parameter(&p, &(text_command.string_value)))
+ {
+ return true;
+ }
+ text_command.name = '\0';
+ command.parameters.push_back(text_command);
+ }
+ else
+ {
+ if (command.command[0] == 'T')
+ {
+ //std::cout << "GcodeParser.try_parse_gcode - T parameter found.\r\n";
+ parsed_command_parameter param;
+
+ if (try_extract_t_parameter(&p, ¶m))
+ {
+ command.parameters.push_back(param);
+ }
+
+ }
+ else
+ {
+ while (true)
+ {
+ //std::cout << "GcodeParser.try_parse_gcode - Trying to extract parameters.\r\n";
+ parsed_command_parameter param;
+ if (try_extract_parameter(&p, ¶m))
+ command.parameters.push_back(param);
+ else
+ {
+ //std::cout << "GcodeParser.try_parse_gcode - No parameters found.\r\n";
+ break;
+ }
+ }
+ }
+ }
+ }
+ try_extract_comment(&p_gcode, &(command.comment));
+
+
+ return command.is_known_command;
+
+}
+
+bool gcode_parser::try_extract_gcode_command(char ** p_p_gcode, std::string * p_command)
+{
+ char * p = *p_p_gcode;
+ char gcode_word;
+ bool found_command = false;
+
+ // Ignore Leading Spaces
+ while (*p == ' ')
+ {
+ p++;
+ }
+ // See if this is an @ command, which can be used in octoprint for controlling octolapse
+ if (*p == '@')
+ {
+ found_command = gcode_parser::try_extract_at_command(&p, p_command);
+ }
+ else
+ {
+
+ }
+ // Deal with case sensitivity
+ if (*p >= 'a' && *p <= 'z')
+ gcode_word = *p - 32;
+ else
+ gcode_word = *p;
+ if (gcode_word == 'G' || gcode_word == 'M' || gcode_word == 'T')
+ {
+ // Set the gcode word of the new command to the current pointer's location and increment both
+ (*p_command).push_back(gcode_word);
+ p++;
+
+ if (gcode_word != 'T')
+ {
+ // the T command is special, it has no address
+
+ // Now look for a command address
+ while ((*p >= '0' && *p <= '9') || *p == ' ') {
+ if (*p != ' ')
+ {
+ found_command = true;
+ (*p_command).push_back(*p++);
+ }
+ else if (found_command)
+ {
+ // Previously we just ignored all spaces,
+ // but for the command itself, it might be a good idea
+ // to assume the space is important.
+ // instead, keep moving forward until no spaces are found
+ while (*p == ' ')
+ {
+ p++;
+ }
+ break;
+ }
+ else
+ {
+ // a space was encountered, but no command was found.
+ // increment the pointer and continue to search
+ // for an address
+ ++p;
+ }
+ }
+ if (*p == '.') {
+ (*p_command).push_back(*p++);
+ found_command = false;
+ while ((*p >= '0' && *p <= '9') || *p == ' ') {
+ if (*p != ' ')
+ {
+ found_command = true;
+ (*p_command).push_back(*p++);
+ }
+ else
+ ++p;
+ }
+ }
+ }
+ else
+ {
+ // peek at the next character and see if it is either a number, a question mark, a c, x, or integer.
+ // Use a different pointer so as not to mess up parameter parsing
+ char * p_t = p;
+ // skip any whitespace
+ // Ignore Leading Spaces
+ while (*p_t == ' ' || *p_t == '\t')
+ {
+ p_t++;
+ }
+ // create a char to hold the t parameter
+ char t_param = '\0';
+ //
+ if (*p_t >= 'a' && *p_t <= 'z')
+ t_param = *p_t - 32;
+ else
+ t_param = *p_t;
+
+
+ if (t_param == 'C' || t_param == 'X' || t_param == '?')
+ {
+ p_t++;
+ // The next letter looks good! Now see if there are any other characters before the end of the line (excluding comments)
+ while (*p_t == ' ' || *p_t == '\t')
+ {
+ p_t++;
+ }
+ if (*p_t == ';' || *p_t == '\0')
+ found_command = true;
+ }
+ else if (t_param >= '0' && t_param <= '9')
+ {
+ found_command = true;
+ }
+ }
+ }
+ *p_p_gcode = p;
+ return found_command;
+}
+
+bool gcode_parser::try_extract_at_command(char ** p_p_gcode, std::string * p_command)
+{
+ char *p = *p_p_gcode;
+ bool found_command = false;
+ while (*p != '\0' && *p != ';' && *p!= ' ')
+ {
+ if (!found_command)
+ {
+ found_command = true;
+ }
+ if (*p >= 'a' && *p <= 'z')
+ (*p_command).push_back(*p++ - 32);
+ else
+ (*p_command).push_back(*p++);
+
+ }
+ *p_p_gcode = p;
+ return found_command;
+
+}
+
+bool gcode_parser::try_extract_unsigned_long(char ** p_p_gcode, unsigned long * p_value) {
+ char * p = *p_p_gcode;
+ unsigned int r = 0;
+ bool found_numbers = false;
+ // skip any leading whitespace
+ while (*p == ' ')
+ ++p;
+
+ while ((*p >= '0' && *p <= '9') || *p == ' ') {
+ if (*p != ' ')
+ {
+ found_numbers = true;
+ r = static_cast((r * 10.0) + (*p - '0'));
+ }
+ ++p;
+ }
+ if (found_numbers)
+ {
+ *p_value = r;
+ *p_p_gcode = p;
+ }
+
+ return found_numbers;
+}
+
+double gcode_parser::ten_pow(unsigned short n) {
+ double r = 1.0;
+
+ while (n > 0) {
+ r *= 10;
+ --n;
+ }
+
+ return r;
+}
+
+bool gcode_parser::try_extract_double(char ** p_p_gcode, double * p_double) const
+{
+ char * p = *p_p_gcode;
+ bool neg = false;
+ double r = 0;
+ bool found_numbers = false;
+ // skip any leading whitespace
+ while (*p == ' ')
+ ++p;
+ // Check for negative sign
+ if (*p == '-') {
+ neg = true;
+ ++p;
+ while (*p == ' ')
+ ++p;
+ }
+ else if (*p == '+') {
+ // Positive sign doesn't affect anything since we assume positive
+ ++p;
+ while (*p == ' ')
+ ++p;
+ }
+ // skip any additional whitespace
+
+
+ while ((*p >= '0' && *p <= '9') || *p == ' ') {
+ if (*p != ' ')
+ {
+ found_numbers = true;
+ r = (r*10.0) + (*p - '0');
+ }
+ ++p;
+ }
+ if (*p == '.') {
+ double f = 0.0;
+ unsigned short n = 0;
+ ++p;
+ while ((*p >= '0' && *p <= '9') || *p == ' ') {
+ if (*p != ' ')
+ {
+ found_numbers = true;
+ f = (f*10.0) + (*p - '0');
+ ++n;
+ }
+ ++p;
+ }
+ //r += f / pow(10.0, n);
+ r += f / ten_pow(n);
+ }
+ if (neg) {
+ r = -r;
+ }
+ if (found_numbers)
+ {
+ *p_double = r;
+ *p_p_gcode = p;
+ }
+
+ return found_numbers;
+}
+
+bool gcode_parser::try_extract_text_parameter(char ** p_p_gcode, std::string * p_parameter)
+{
+ // Skip initial whitespace
+ //std::cout << "GcodeParser.try_extract_parameter - Trying to extract a text parameter from " << *p_p_gcode << "\r\n";
+ char * p = *p_p_gcode;
+
+ // Ignore Leading Spaces
+ while (*p == ' ')
+ {
+ p++;
+ }
+ // Add all values, stop at end of string or when we hit a ';'
+
+ while (*p != '\0' && *p != ';')
+ {
+ (*p_parameter).push_back(*p++);
+ }
+ *p_p_gcode = p;
+ return true;
+
+}
+
+bool gcode_parser::try_extract_octolapse_parameter(char ** p_p_gcode, parsed_command_parameter * p_parameter)
+{
+ p_parameter->name = "";
+ p_parameter->value_type = 'N';
+ // Skip initial whitespace
+ //std::cout << "GcodeParser.try_extract_parameter - Trying to extract a text parameter from " << *p_p_gcode << "\r\n";
+ char * p = *p_p_gcode;
+ bool has_found_parameter = false;
+ // Ignore Leading Spaces
+ while (*p == ' ')
+ {
+ p++;
+ }
+ // extract name, make all caps.
+ while (*p != '\0' && *p != ';' && *p != ' ')
+ {
+ if (!has_found_parameter)
+ {
+ has_found_parameter = true;
+ }
+
+ if (*p >= 'a' && *p <= 'z')
+ {
+ p_parameter->name.push_back(*p++ - 32);
+ }
+ else
+ {
+ p_parameter->name.push_back(*p++);
+ }
+ }
+ // Todo: Handle any otolapse commands require a string parameter
+ /*
+ // Ignore spaces after the command name
+ while (*p == ' ')
+ {
+ p++;
+ }
+ // Extract the value (we may do this per command in the future). This will output mixed case.
+ bool has_parameter_value = false;
+ while (*p != '\0' && *p != ';')
+ {
+ if (!has_parameter_value)
+ {
+ p_parameter->value_type = 'S';
+ has_parameter_value = true;
+ }
+ p_parameter->string_value.push_back(*p++);
+ }
+ if (has_parameter_value)
+ {
+ p_parameter->string_value = utilities::rtrim(p_parameter->string_value);
+ }
+ */
+ *p_p_gcode = p;
+ return has_found_parameter;
+}
+
+bool gcode_parser::try_extract_parameter(char ** p_p_gcode, parsed_command_parameter * parameter) const
+{
+ //std::cout << "GcodeParser.try_extract_parameter - Trying to extract a parameter from " << *p_p_gcode << "\r\n";
+ char * p = *p_p_gcode;
+
+ // Ignore Leading Spaces
+ while (*p == ' ')
+ {
+ p++;
+ }
+
+ // Deal with case sensitivity
+ if (*p >= 'a' && *p <= 'z')
+ parameter->name = *p++ - 32;
+ else if (*p >= 'A' && *p <= 'Z')
+ parameter->name = *p++;
+ else
+ return false;
+ // TODO: See if unsigned long works....
+
+ // Add all values, stop at end of string or when we hit a ';'
+ if (try_extract_double(&p,&(parameter->double_value)))
+ {
+ parameter->value_type = 'F';
+ }
+ else
+ {
+ if(try_extract_text_parameter(&p, &(parameter->string_value)))
+ {
+ parameter->value_type = 'S';
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ *p_p_gcode = p;
+ return true;
+
+}
+
+bool gcode_parser::try_extract_t_parameter(char ** p_p_gcode, parsed_command_parameter * parameter)
+{
+ //std::cout << "Trying to extract a T parameter from " << *p_p_gcode << "\r\n";
+ char * p = *p_p_gcode;
+ parameter->name = 'T';
+ // Ignore Leading Spaces
+ while (*p == L' ')
+ {
+ p++;
+ }
+
+ if (*p == L'c' || *p == L'C')
+ {
+ //std::cout << "Found C value for T parameter\r\n";
+ parameter->string_value = "C";
+ parameter->value_type = 'S';
+ }
+ else if (*p == L'x' || *p == L'X')
+ {
+ //std::cout << "Found X value for T parameter\r\n";
+ parameter->string_value = "X";
+ parameter->value_type = 'S';
+ }
+ else if (*p == L'?')
+ {
+ //std::cout << "Found ? value for T parameter\r\n";
+ parameter->string_value = "?";
+ parameter->value_type = 'S';
+ }
+ else
+ {
+ //std::cout << "No char t parameter found, looking for unsigned int values.\r\n";
+ if(!try_extract_unsigned_long(&p,&(parameter->unsigned_long_value)))
+ {
+ //std::cout << "No parameter for the T command.\r\n";
+ return false;
+ }
+ parameter->value_type = 'U';
+ }
+ return true;
+}
+
+bool gcode_parser::try_extract_comment(char ** p_p_gcode, std::string * p_comment)
+{
+ // Skip initial whitespace
+ //std::cout << "GcodeParser.try_extract_parameter - Trying to extract a text parameter from " << *p_p_gcode << "\r\n";
+ char * p = *p_p_gcode;
+
+ bool found_comment = false;
+ // Hunt for the comment (semicolon)
+ while (*p != '\0' && !found_comment)
+ {
+ if (*p == ';')
+ {
+ found_comment = true;
+ }
+ p++;
+ }
+
+ // Add all values, stop at end of string or when we hit a ';'
+ /*while (*p == ';' || *p == ' ')
+ {
+ p++;
+ }*/
+ // Add all characters until we hit the null terminator
+ while (*p != '\0')
+ {
+ if (*p != '\r' && *p != '\n')
+ {
+ // Dont't add line breaks
+ (*p_comment).push_back(*p++);
+ }
+ else
+ p++;
+ }
+ *p_p_gcode = p;
+ return p_comment->length() != 0;
+
+}
\ No newline at end of file
diff --git a/GcodeProcessorLib/gcode_parser.h b/GcodeProcessorLib/gcode_parser.h
new file mode 100644
index 0000000..50cb01b
--- /dev/null
+++ b/GcodeProcessorLib/gcode_parser.h
@@ -0,0 +1,56 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GCODE_PARSER_H
+#define GCODE_PARSER_H
+#include
+#include
+#include
+#include "parsed_command.h"
+#include "parsed_command_parameter.h"
+static const std::string GCODE_WORDS = "GMT";
+
+class gcode_parser
+{
+public:
+ gcode_parser();
+ ~gcode_parser();
+ bool try_parse_gcode(const char * gcode, parsed_command & command);
+ parsed_command parse_gcode(const char * gcode);
+private:
+ gcode_parser(const gcode_parser &source);
+ // Variables and lookups
+ std::set text_only_functions_;
+ std::set parsable_commands_;
+ // Functions
+ bool try_extract_double(char ** p_p_gcode, double * p_double) const;
+ static bool try_extract_gcode_command(char ** p_p_gcode, std::string * p_command);
+ static bool try_extract_text_parameter(char ** p_p_gcode, std::string * p_parameter);
+ bool try_extract_parameter(char ** p_p_gcode, parsed_command_parameter * parameter) const;
+ static bool try_extract_t_parameter(char ** p_p_gcode, parsed_command_parameter * parameter);
+ static bool try_extract_unsigned_long(char ** p_p_gcode, unsigned long * p_value);
+ double static ten_pow(unsigned short n);
+ bool try_extract_comment(char ** p_p_gcode, std::string * p_comment);
+ static bool try_extract_at_command(char ** p_p_gcode, std::string * p_command);
+ bool try_extract_octolapse_parameter(char ** p_p_gcode, parsed_command_parameter * p_parameter);
+};
+#endif
diff --git a/GcodeProcessorLib/gcode_position.cpp b/GcodeProcessorLib/gcode_position.cpp
new file mode 100644
index 0000000..3fc661f
--- /dev/null
+++ b/GcodeProcessorLib/gcode_position.cpp
@@ -0,0 +1,1418 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "gcode_position.h"
+#include "utilities.h"
+#include
+#include
+#include
+gcode_position_args::gcode_position_args(const gcode_position_args &pos_args)
+{
+ position_buffer_size = pos_args.position_buffer_size;
+ shared_extruder = pos_args.shared_extruder;
+ autodetect_position = pos_args.autodetect_position;
+ is_circular_bed = pos_args.is_circular_bed;
+ home_x = pos_args.home_x;
+ home_y = pos_args.home_y;
+ home_z = pos_args.home_z;
+ home_x_none = pos_args.home_x_none;
+ home_y_none = pos_args.home_y_none;
+ home_z_none = pos_args.home_z_none;
+
+ priming_height = pos_args.priming_height;
+ minimum_layer_height = pos_args.minimum_layer_height;
+ height_increment = pos_args.height_increment;
+ g90_influences_extruder = pos_args.g90_influences_extruder;
+ xyz_axis_default_mode = pos_args.xyz_axis_default_mode;
+ e_axis_default_mode = pos_args.e_axis_default_mode;
+ units_default = pos_args.units_default;
+ is_bound_ = pos_args.is_bound_;
+ x_min = pos_args.x_min;
+ x_max = pos_args.x_max;
+ y_min = pos_args.y_min;
+ y_max = pos_args.y_max;
+ z_min = pos_args.z_min;
+ z_max = pos_args.z_max;
+ snapshot_x_min = pos_args.snapshot_x_min;
+ snapshot_x_max = pos_args.snapshot_x_max;
+ snapshot_y_min = pos_args.snapshot_y_min;
+ snapshot_y_max = pos_args.snapshot_y_max;
+ snapshot_z_min = pos_args.snapshot_z_min;
+ snapshot_z_max = pos_args.snapshot_z_max;
+
+ default_extruder = pos_args.default_extruder;
+ zero_based_extruder = pos_args.zero_based_extruder;
+ num_extruders = pos_args.num_extruders;
+ retraction_lengths = NULL;
+ z_lift_heights = NULL;
+ x_firmware_offsets = NULL;
+ y_firmware_offsets = NULL;
+ set_num_extruders(pos_args.num_extruders);
+
+ for (int index = 0; index < pos_args.num_extruders; index++)
+ {
+ retraction_lengths[index] = pos_args.retraction_lengths[index];
+ z_lift_heights[index] = pos_args.z_lift_heights[index];
+ if (!pos_args.shared_extruder)
+ {
+ x_firmware_offsets[index] = pos_args.x_firmware_offsets[index];
+ y_firmware_offsets[index] = pos_args.y_firmware_offsets[index];
+ }
+ else
+ {
+ x_firmware_offsets[index] = 0;
+ y_firmware_offsets[index] = 0;
+ }
+ }
+ std::vector location_detection_commands; // Final list of location detection commands
+}
+
+gcode_position_args& gcode_position_args::operator=(const gcode_position_args& pos_args)
+{
+ position_buffer_size = pos_args.position_buffer_size;
+ shared_extruder = pos_args.shared_extruder;
+ autodetect_position = pos_args.autodetect_position;
+ is_circular_bed = pos_args.is_circular_bed;
+ home_x = pos_args.home_x;
+ home_y = pos_args.home_y;
+ home_z = pos_args.home_z;
+ home_x_none = pos_args.home_x_none;
+ home_y_none = pos_args.home_y_none;
+ home_z_none = pos_args.home_z_none;
+
+ priming_height = pos_args.priming_height;
+ minimum_layer_height = pos_args.minimum_layer_height;
+ height_increment = pos_args.height_increment;
+ g90_influences_extruder = pos_args.g90_influences_extruder;
+ xyz_axis_default_mode = pos_args.xyz_axis_default_mode;
+ e_axis_default_mode = pos_args.e_axis_default_mode;
+ units_default = pos_args.units_default;
+ is_bound_ = pos_args.is_bound_;
+ x_min = pos_args.x_min;
+ x_max = pos_args.x_max;
+ y_min = pos_args.y_min;
+ y_max = pos_args.y_max;
+ z_min = pos_args.z_min;
+ z_max = pos_args.z_max;
+ snapshot_x_min = pos_args.snapshot_x_min;
+ snapshot_x_max = pos_args.snapshot_x_max;
+ snapshot_y_min = pos_args.snapshot_y_min;
+ snapshot_y_max = pos_args.snapshot_y_max;
+ snapshot_z_min = pos_args.snapshot_z_min;
+ snapshot_z_max = pos_args.snapshot_z_max;
+
+ default_extruder = pos_args.default_extruder;
+ zero_based_extruder = pos_args.zero_based_extruder;
+ num_extruders = pos_args.num_extruders;
+ delete_retraction_lengths();
+ delete_x_firmware_offsets();
+ delete_y_firmware_offsets();
+ delete_z_lift_heights();
+ set_num_extruders(pos_args.num_extruders);
+ // copy extruder specific members
+ for(int index=0; index < pos_args.num_extruders; index++)
+ {
+ retraction_lengths[index] = pos_args.retraction_lengths[index];
+ z_lift_heights[index] = pos_args.z_lift_heights[index];
+ if (!pos_args.shared_extruder)
+ {
+ x_firmware_offsets[index] = pos_args.x_firmware_offsets[index];
+ y_firmware_offsets[index] = pos_args.y_firmware_offsets[index];
+ }
+ else
+ {
+ x_firmware_offsets[index] = 0;
+ y_firmware_offsets[index] = 0;
+ }
+ }
+ std::vector location_detection_commands; // Final list of location detection commands
+ return *this;
+}
+
+void gcode_position_args::set_num_extruders(int num_extruders_)
+{
+ delete_retraction_lengths();
+ delete_z_lift_heights();
+ delete_x_firmware_offsets();
+ delete_y_firmware_offsets();
+ num_extruders = num_extruders_;
+
+ retraction_lengths = new double[num_extruders_];
+ z_lift_heights = new double[num_extruders_];
+ x_firmware_offsets = new double[num_extruders_];
+ y_firmware_offsets = new double[num_extruders_];
+ // initialize arrays
+ for (int index=0; index < num_extruders; index++)
+ {
+ retraction_lengths[index] = 0.0;
+ z_lift_heights[index] = 0.0;
+ x_firmware_offsets[index] = 0.0;
+ y_firmware_offsets[index] = 0.0;
+ }
+}
+
+void gcode_position_args::delete_retraction_lengths()
+{
+ if (retraction_lengths != NULL)
+ {
+ delete[] retraction_lengths;
+ retraction_lengths = NULL;
+ }
+}
+
+void gcode_position_args::delete_z_lift_heights()
+{
+ if (z_lift_heights != NULL)
+ {
+ delete[] z_lift_heights;
+ z_lift_heights = NULL;
+ }
+}
+
+void gcode_position_args::delete_x_firmware_offsets()
+{
+ if (x_firmware_offsets != NULL)
+ {
+ delete[] x_firmware_offsets;
+ x_firmware_offsets = NULL;
+ }
+}
+
+void gcode_position_args::delete_y_firmware_offsets()
+{
+ if (y_firmware_offsets != NULL)
+ {
+ delete[] y_firmware_offsets;
+ y_firmware_offsets = NULL;
+ }
+}
+
+gcode_position::gcode_position()
+{
+ position_buffer_size_ = 50;
+ positions_ = new position[position_buffer_size_];
+ autodetect_position_ = false;
+ home_x_ = 0;
+ home_y_ = 0;
+ home_z_ = 0;
+ home_x_none_ = true;
+ home_y_none_ = true;
+ home_z_none_ = true;
+ retraction_lengths_ = NULL;
+ z_lift_heights_ = NULL;
+ shared_extruder_ = false;
+ set_num_extruders(0);
+ zero_based_extruder_ = true;
+ priming_height_ = 0;
+ minimum_layer_height_ = 0;
+ height_increment_ = 0;
+ g90_influences_extruder_ = false;
+ e_axis_default_mode_ = "absolute";
+ xyz_axis_default_mode_ = "absolute";
+ units_default_ = "millimeters";
+ gcode_functions_ = get_gcode_functions();
+
+ is_bound_ = false;
+ snapshot_x_min_ = 0;
+ snapshot_x_max_ = 0;
+ snapshot_y_min_ = 0;
+ snapshot_y_max_ = 0;
+ snapshot_z_min_ = 0;
+ snapshot_z_max_ = 0;
+
+ x_min_ = 0;
+ x_max_ = 0;
+ y_min_ = 0;
+ y_max_ = 0;
+ z_min_ = 0;
+ z_max_ = 0;
+ is_circular_bed_ = false;
+
+ cur_pos_ = -1;
+ num_pos_ = 0;
+ for(int index = 0; index < position_buffer_size_; index ++)
+ {
+ position initial_pos(num_extruders_);
+ initial_pos.set_xyz_axis_mode(xyz_axis_default_mode_);
+ initial_pos.set_e_axis_mode(e_axis_default_mode_);
+ initial_pos.set_units_default(units_default_);
+ add_position(initial_pos);
+ }
+ num_pos_ = 0;
+}
+
+gcode_position::gcode_position(gcode_position_args args)
+{
+ position_buffer_size_ = args.position_buffer_size;
+ positions_ = new position[args.position_buffer_size] ;
+ autodetect_position_ = args.autodetect_position;
+ home_x_ = args.home_x;
+ home_y_ = args.home_y;
+ home_z_ = args.home_z;
+ home_x_none_ = args.home_x_none;
+ home_y_none_ = args.home_y_none;
+ home_z_none_ = args.home_z_none;
+ retraction_lengths_ = NULL;
+ z_lift_heights_ = NULL;
+ // Configure Extruders
+ shared_extruder_ = args.shared_extruder;
+ set_num_extruders(args.num_extruders);
+ zero_based_extruder_ = args.zero_based_extruder;
+ // Set the current extruder to the default extruder (0 based)
+ int current_extruder = args.default_extruder;
+ // make sure our current extruder is between 0 and num_extruders - 1
+ if (current_extruder < 0)
+ {
+ current_extruder = 0;
+ }
+ else if (current_extruder > args.num_extruders - 1)
+ {
+ current_extruder = args.num_extruders - 1;
+ }
+
+ // copy the retraction lengths array
+ for (int index=0; index < args.num_extruders; index++)
+ {
+ retraction_lengths_[index] = args.retraction_lengths[index];
+ }
+ // Copy the z_lift_heights array from the arguments
+ for (int index = 0; index < args.num_extruders; index++)
+ {
+ z_lift_heights_[index] = args.z_lift_heights[index];
+ }
+ // Copy the firmware offsets
+ for (int index = 0; index < args.num_extruders; index++)
+ {
+ retraction_lengths_[index] = args.retraction_lengths[index];
+ }
+
+ priming_height_ = args.priming_height;
+ minimum_layer_height_ = args.minimum_layer_height;
+ height_increment_ = args.height_increment;
+ g90_influences_extruder_ = args.g90_influences_extruder;
+ e_axis_default_mode_ = args.e_axis_default_mode;
+ xyz_axis_default_mode_ = args.xyz_axis_default_mode;
+ units_default_ = args.units_default;
+ gcode_functions_ = get_gcode_functions();
+
+ is_bound_ = args.is_bound_;
+ snapshot_x_min_ = args.snapshot_x_min;
+ snapshot_x_max_ = args.snapshot_x_max;
+ snapshot_y_min_ = args.snapshot_y_min;
+ snapshot_y_max_ = args.snapshot_y_max;
+ snapshot_z_min_ = args.snapshot_z_min;
+ snapshot_z_max_ = args.snapshot_z_max;
+
+ x_min_ = args.x_min;
+ x_max_ = args.x_max;
+ y_min_ = args.y_min;
+ y_max_ = args.y_max;
+ z_min_ = args.z_min;
+ z_max_ = args.z_max;
+
+ is_circular_bed_ = args.is_circular_bed;
+
+ cur_pos_ = -1;
+ num_pos_ = 0;
+ num_extruders_ = args.num_extruders;
+
+ // Configure the initial position
+ position initial_pos(num_extruders_);
+ initial_pos.set_xyz_axis_mode(xyz_axis_default_mode_);
+ initial_pos.set_e_axis_mode(e_axis_default_mode_);
+ initial_pos.set_units_default(units_default_);
+ initial_pos.current_tool = current_extruder;
+ for (int index = 0; index < args.num_extruders; index++)
+ {
+ initial_pos.p_extruders[index].x_firmware_offset = args.x_firmware_offsets[index];
+ initial_pos.p_extruders[index].y_firmware_offset = args.y_firmware_offsets[index];
+ }
+
+ for (int index = 0; index < position_buffer_size_; index++)
+ {
+
+ add_position(initial_pos);
+ }
+ num_pos_ = 0;
+}
+
+gcode_position::gcode_position(const gcode_position &source)
+{
+ // Private copy constructor - you can't copy this class
+}
+
+gcode_position::~gcode_position()
+{
+ if (positions_ != NULL)
+ {
+ delete [] positions_;
+ positions_ = NULL;
+ }
+ delete_retraction_lengths_();
+ delete_z_lift_heights_();
+}
+
+void gcode_position::set_num_extruders(int num_extruders)
+{
+ delete_retraction_lengths_();
+ delete_z_lift_heights_();
+ if (shared_extruder_)
+ {
+ num_extruders_ = 1;
+ }
+ else
+ {
+ num_extruders_ = num_extruders;
+ }
+ retraction_lengths_ = new double[num_extruders];
+ z_lift_heights_ = new double[num_extruders];
+}
+
+void gcode_position::delete_retraction_lengths_()
+{
+ if (retraction_lengths_ != NULL)
+ {
+ delete[] retraction_lengths_;
+ retraction_lengths_ = NULL;
+ }
+}
+
+void gcode_position::delete_z_lift_heights_()
+{
+ if (z_lift_heights_ != NULL)
+ {
+ delete[] z_lift_heights_;
+ z_lift_heights_ = NULL;
+ }
+}
+
+int gcode_position::get_num_positions()
+{
+ return num_pos_;
+}
+
+void gcode_position::add_position(position& pos)
+{
+ cur_pos_ = (cur_pos_+1) % position_buffer_size_;
+ positions_[cur_pos_] = pos;
+ if (num_pos_ < position_buffer_size_)
+ num_pos_++;
+}
+
+void gcode_position::add_position(parsed_command& cmd)
+{
+ const int prev_pos = cur_pos_;
+ cur_pos_ = (cur_pos_+1) % position_buffer_size_;
+ positions_[cur_pos_] = positions_[prev_pos];
+ positions_[cur_pos_].reset_state();
+ positions_[cur_pos_].command = cmd;
+ positions_[cur_pos_].is_empty = false;
+ if (num_pos_ < position_buffer_size_)
+ num_pos_++;
+}
+
+position gcode_position::get_position(int index)
+{
+ return positions_[(cur_pos_ - index + position_buffer_size_) % position_buffer_size_];
+}
+
+position gcode_position::get_current_position()
+{
+ return get_position(0);
+}
+
+position gcode_position::get_previous_position()
+{
+ return get_position(1);
+}
+
+position * gcode_position::get_position_ptr(int index)
+{
+ return &positions_[(cur_pos_ - index + position_buffer_size_) % position_buffer_size_];
+}
+
+position * gcode_position::get_current_position_ptr()
+{
+ return get_position_ptr(0);
+}
+
+position * gcode_position::get_previous_position_ptr()
+{
+
+ return get_position_ptr(1);
+}
+
+void gcode_position::update(parsed_command& command, const long file_line_number, const long gcode_number, const long file_position)
+{
+
+ /*if (command.is_empty)
+ {
+ // process any comment sections
+ comment_processor_.update(command.comment);
+ return;
+ }*/
+
+ add_position(command);
+ position * p_current_pos = get_current_position_ptr();
+ position * p_previous_pos = get_previous_position_ptr();
+ p_current_pos->file_line_number = file_line_number;
+ p_current_pos->gcode_number = gcode_number;
+ p_current_pos->file_position = file_position;
+ comment_processor_.update(*p_current_pos);
+
+ if (!command.is_known_command || command.is_empty)
+ return;
+
+ // Does our function exist in our functions map?
+ gcode_functions_iterator_ = gcode_functions_.find(command.command);
+
+ if (gcode_functions_iterator_ != gcode_functions_.end())
+ {
+ p_current_pos->gcode_ignored = false;
+ // Execute the function to process this gcode
+ const pos_function_type func = gcode_functions_iterator_->second;
+ (this->*func)(p_current_pos, command);
+ // calculate z and e relative distances
+ p_current_pos->get_current_extruder().e_relative = (p_current_pos->get_current_extruder().e - p_previous_pos->get_extruder(p_current_pos->current_tool).e);
+ p_current_pos->z_relative = (p_current_pos->z - p_previous_pos->z);
+ // Have the XYZ positions changed after processing a command ?
+
+ p_current_pos->has_xy_position_changed = (
+ !utilities::is_equal(p_current_pos->x, p_previous_pos->x) ||
+ !utilities::is_equal(p_current_pos->y, p_previous_pos->y)
+ );
+ p_current_pos->has_position_changed = (
+ p_current_pos->has_xy_position_changed ||
+ !utilities::is_equal(p_current_pos->z, p_previous_pos->z) ||
+ !utilities::is_zero(p_current_pos->get_current_extruder().e_relative) ||
+ p_current_pos->x_null != p_previous_pos->x_null ||
+ p_current_pos->y_null != p_previous_pos->y_null ||
+ p_current_pos->z_null != p_previous_pos->z_null);
+
+ // see if our position is homed
+ if (!p_current_pos->has_definite_position)
+ {
+ p_current_pos->has_definite_position = (
+ //p_current_pos->x_homed_ &&
+ //p_current_pos->y_homed_ &&
+ //p_current_pos->z_homed_ &&
+ p_current_pos->is_metric &&
+ !p_current_pos->is_metric_null &&
+ !p_current_pos->x_null &&
+ !p_current_pos->y_null &&
+ !p_current_pos->z_null &&
+ !p_current_pos->is_relative_null &&
+ !p_current_pos->is_extruder_relative_null);
+ }
+ }
+
+ if (p_current_pos->has_position_changed)
+ {
+ p_current_pos->get_current_extruder().extrusion_length_total += p_current_pos->get_current_extruder().e_relative;
+
+ if (
+ utilities::greater_than(p_current_pos->get_current_extruder().e_relative, 0) &&
+ p_previous_pos->current_tool == p_current_pos->current_tool &&
+ // notice we can use the previous position's current extruder since we've made sure they are using the same tool
+ p_previous_pos->get_current_extruder().is_extruding &&
+ !p_previous_pos->get_current_extruder().is_extruding_start)
+ {
+ // A little shortcut if we know we were extruding (not starting extruding) in the previous command
+ // This lets us skip a lot of the calculations for the extruder, including the state calculation
+ p_current_pos->get_current_extruder().extrusion_length = p_current_pos->get_current_extruder().e_relative;
+ }
+ else
+ {
+
+ // Update retraction_length and extrusion_length
+ p_current_pos->get_current_extruder().retraction_length = p_current_pos->get_current_extruder().retraction_length - p_current_pos->get_current_extruder().e_relative;
+ if (utilities::less_than_or_equal(p_current_pos->get_current_extruder().retraction_length, 0))
+ {
+ // we can use the negative retraction length to calculate our extrusion length!
+ p_current_pos->get_current_extruder().extrusion_length = -1.0 * p_current_pos->get_current_extruder().retraction_length;
+ // set the retraction length to 0 since we are extruding
+ p_current_pos->get_current_extruder().retraction_length = 0;
+ }
+ else
+ p_current_pos->get_current_extruder().extrusion_length = 0;
+
+ // calculate deretraction length
+ if (utilities::greater_than(p_previous_pos->get_extruder(p_current_pos->current_tool).retraction_length, p_current_pos->get_current_extruder().retraction_length))
+ {
+ p_current_pos->get_current_extruder().deretraction_length = p_previous_pos->get_extruder(p_current_pos->current_tool).retraction_length - p_current_pos->get_current_extruder().retraction_length;
+ }
+ else
+ p_current_pos->get_current_extruder().deretraction_length = 0;
+
+ // *************Calculate extruder state*************
+ // rounding should all be done by now
+ if(p_current_pos->current_tool == p_previous_pos->current_tool)
+ {
+ // On a toolchange some flags are not possible, so don't change them.
+ // these flags include like is_extruding, is_extruding_start, is_retracting_start, is_retracting, is_deretracting_start and is_deretracting
+ // Note that it's ok to use the previous pos current extruder since we've made sure the current tool is identical
+ p_current_pos->get_current_extruder().is_extruding_start = utilities::greater_than(p_current_pos->get_current_extruder().extrusion_length, 0) && !p_previous_pos->get_current_extruder().is_extruding;
+ p_current_pos->get_current_extruder().is_extruding = utilities::greater_than(p_current_pos->get_current_extruder().extrusion_length, 0);
+ p_current_pos->get_current_extruder().is_retracting_start = !p_previous_pos->get_current_extruder().is_retracting && utilities::greater_than(p_current_pos->get_current_extruder().retraction_length, 0);
+ p_current_pos->get_current_extruder().is_retracting = utilities::greater_than(p_current_pos->get_current_extruder().retraction_length, p_previous_pos->get_current_extruder().retraction_length);
+ p_current_pos->get_current_extruder().is_deretracting = utilities::greater_than(p_current_pos->get_current_extruder().deretraction_length, p_previous_pos->get_current_extruder().deretraction_length);
+ p_current_pos->get_current_extruder().is_deretracting_start = utilities::greater_than(p_current_pos->get_current_extruder().deretraction_length, 0) && !p_previous_pos->get_current_extruder().is_deretracting;
+ }
+ else
+ {
+ p_current_pos->get_current_extruder().is_extruding_start = false;
+ p_current_pos->get_current_extruder().is_extruding = false;
+ p_current_pos->get_current_extruder().is_retracting_start = false;
+ p_current_pos->get_current_extruder().is_retracting = false;
+ p_current_pos->get_current_extruder().is_deretracting = false;
+ p_current_pos->get_current_extruder().is_deretracting_start = false;
+ }
+ p_current_pos->get_current_extruder().is_primed = utilities::is_zero(p_current_pos->get_current_extruder().extrusion_length) && utilities::is_zero(p_current_pos->get_current_extruder().retraction_length);
+ p_current_pos->get_current_extruder().is_partially_retracted = utilities::greater_than(p_current_pos->get_current_extruder().retraction_length, 0) && utilities::less_than(p_current_pos->get_current_extruder().retraction_length, retraction_lengths_[p_current_pos->current_tool]);
+ p_current_pos->get_current_extruder().is_retracted = utilities::greater_than_or_equal(p_current_pos->get_current_extruder().retraction_length, retraction_lengths_[p_current_pos->current_tool]);
+ p_current_pos->get_current_extruder().is_deretracted = utilities::greater_than(p_previous_pos->get_extruder(p_current_pos->current_tool).retraction_length, 0) && utilities::is_zero(p_current_pos->get_current_extruder().retraction_length);
+ // *************End Calculate extruder state*************
+ }
+
+ // Calcluate position restructions
+ // TODO: INCLUDE POSITION RESTRICTION CALCULATIONS!
+ // Set is_in_bounds_ to false if we're not in bounds, it will be true at this point
+ bool is_in_bounds = true;
+ if (is_bound_)
+ {
+ if (!is_circular_bed_)
+ {
+ is_in_bounds = !(
+ utilities::less_than(p_current_pos->x, snapshot_x_min_) ||
+ utilities::greater_than(p_current_pos->x, snapshot_x_max_) ||
+ utilities::less_than(p_current_pos->y, snapshot_y_min_) ||
+ utilities::greater_than(p_current_pos->y, snapshot_y_max_) ||
+ utilities::less_than(p_current_pos->z, snapshot_z_min_) ||
+ utilities::greater_than(p_current_pos->z, snapshot_z_max_)
+ );
+
+ }
+ else
+ {
+ double r;
+ r = snapshot_x_max_; // good stand in for radius
+ const double dist = sqrt(p_current_pos->x*p_current_pos->x + p_current_pos->y*p_current_pos->y);
+ is_in_bounds = utilities::less_than_or_equal(dist, r);
+
+ }
+ p_current_pos->is_in_bounds = is_in_bounds;
+ }
+
+ // calculate last_extrusion_height and height
+ // If we are extruding on a higher level, or if retract is enabled and the nozzle is primed
+ // adjust the last extrusion height
+ if (utilities::greater_than(p_current_pos->z, p_current_pos->last_extrusion_height))
+ {
+ if (!p_current_pos->z_null)
+ {
+ // detect layer changes/ printer priming/last extrusion height and height
+ // Normally we would only want to use is_extruding, but we can also use is_deretracted if the layer is greater than 0
+ if (p_current_pos->get_current_extruder().is_extruding || (p_current_pos->layer >0 && p_current_pos->get_current_extruder().is_deretracted))
+ {
+ // Is Primed
+ if (!p_current_pos->is_printer_primed)
+ {
+ // We haven't primed yet, check to see if we have priming height restrictions
+ if (utilities::greater_than(priming_height_, 0))
+ {
+ // if a priming height is configured, see if we've extruded below the height
+ if (utilities::less_than(p_current_pos->z, priming_height_))
+ p_current_pos->is_printer_primed = true;
+ }
+ else
+ // if we have no priming height set, just set is_printer_primed = true.
+ p_current_pos->is_printer_primed = true;
+ }
+
+ if (p_current_pos->is_printer_primed && is_in_bounds)
+ {
+ // Update the last extrusion height
+ p_current_pos->last_extrusion_height = p_current_pos->z;
+ p_current_pos->last_extrusion_height_null = false;
+
+ // Calculate current height
+ if (utilities::greater_than_or_equal(p_current_pos->z, p_previous_pos->height + minimum_layer_height_))
+ {
+ p_current_pos->height = p_current_pos->z;
+ p_current_pos->is_layer_change = true;
+ p_current_pos->layer++;
+ if (height_increment_ != 0)
+ {
+ const double increment_double = p_current_pos->height / height_increment_;
+ const int increment = utilities::round_up_to_int(increment_double);
+ if (increment > p_current_pos->height_increment && increment > 1)
+ {
+ p_current_pos->height_increment = increment;
+ p_current_pos->is_height_increment_change = true;
+ p_current_pos->height_increment_change_count++;
+ }
+ }
+ }
+ }
+ }
+
+ // calculate is_zhop
+ if (p_current_pos->get_current_extruder().is_extruding || p_current_pos->z_null || p_current_pos->last_extrusion_height_null)
+ p_current_pos->is_zhop = false;
+ else
+ p_current_pos->is_zhop = utilities::greater_than_or_equal(p_current_pos->z - p_current_pos->last_extrusion_height, z_lift_heights_[p_current_pos->current_tool]);
+ }
+
+ }
+
+
+
+ }
+}
+
+void gcode_position::undo_update()
+{
+ if (num_pos_ != 0)
+ {
+ cur_pos_ = (cur_pos_ - 1 + position_buffer_size_) % position_buffer_size_;
+ num_pos_--;
+ }
+}
+
+position* gcode_position::undo_update(int num_updates)
+{
+ if (num_updates < 1)
+ return NULL;
+
+ // Create an array of position pointers that will contain the removed positions
+ position* p_undo_positions = new position[num_updates];
+
+ // add the positions we will undo to the array
+ for (int index = 0; index < num_updates; index++)
+ {
+ p_undo_positions[index] = get_position(index);
+ }
+
+ if (num_pos_ < num_updates)
+ {
+ num_pos_ = 0;
+ cur_pos_ = 0;
+ }
+ else
+ {
+ cur_pos_ = (cur_pos_ - num_updates + position_buffer_size_) % position_buffer_size_;
+ num_pos_ -= num_updates;
+ }
+ return p_undo_positions;
+
+}
+
+// Private Members
+std::map gcode_position::get_gcode_functions()
+{
+ std::map newMap;
+ newMap.insert(std::make_pair("G0", &gcode_position::process_g0_g1));
+ newMap.insert(std::make_pair("G1", &gcode_position::process_g0_g1));
+ newMap.insert(std::make_pair("G2", &gcode_position::process_g2));
+ newMap.insert(std::make_pair("G3", &gcode_position::process_g3));
+ newMap.insert(std::make_pair("G10", &gcode_position::process_g10));
+ newMap.insert(std::make_pair("G11", &gcode_position::process_g11));
+ newMap.insert(std::make_pair("G20", &gcode_position::process_g20));
+ newMap.insert(std::make_pair("G21", &gcode_position::process_g21));
+ newMap.insert(std::make_pair("G28", &gcode_position::process_g28));
+ newMap.insert(std::make_pair("G90", &gcode_position::process_g90));
+ newMap.insert(std::make_pair("G91", &gcode_position::process_g91));
+ newMap.insert(std::make_pair("G92", &gcode_position::process_g92));
+ newMap.insert(std::make_pair("M82", &gcode_position::process_m82));
+ newMap.insert(std::make_pair("M83", &gcode_position::process_m83));
+ newMap.insert(std::make_pair("M207", &gcode_position::process_m207));
+ newMap.insert(std::make_pair("M208", &gcode_position::process_m208));
+ newMap.insert(std::make_pair("M218", &gcode_position::process_m218));
+ newMap.insert(std::make_pair("M563", &gcode_position::process_m563));
+ newMap.insert(std::make_pair("T", &gcode_position::process_t));
+ return newMap;
+}
+
+void gcode_position::update_position(
+ position* pos,
+ const double x,
+ const bool update_x,
+ const double y,
+ const bool update_y,
+ const double z,
+ const bool update_z,
+ const double e,
+ const bool update_e,
+ const double f,
+ const bool update_f,
+ const bool force,
+ const bool is_g1_g0) const
+{
+ if (is_g1_g0)
+ {
+ if (!update_e)
+ {
+ if (update_z)
+ {
+ pos->is_xyz_travel = (update_x || update_y);
+ }
+ else
+ {
+ pos->is_xy_travel = (update_x || update_y);
+ }
+ }
+
+ }
+ if (update_f)
+ {
+ pos->f = f;
+ pos->f_null = false;
+ }
+
+ if (force)
+ {
+ if (update_x)
+ {
+ pos->x = x + pos->x_offset - pos->x_firmware_offset;
+ pos->x_null = false;
+ }
+ if (update_y)
+ {
+ pos->y = y + pos->y_offset - pos->y_firmware_offset;
+ pos->y_null = false;
+ }
+ if (update_z)
+ {
+ pos->z = z + pos->z_offset - pos->z_firmware_offset;
+ pos->z_null = false;
+ }
+ // note that e cannot be null and starts at 0
+ if (update_e)
+ pos->get_current_extruder().e = e + pos->get_current_extruder().e_offset;
+ return;
+ }
+
+ if (!pos->is_relative_null)
+ {
+ if (pos->is_relative) {
+ if (update_x)
+ {
+ if (!pos->x_null)
+ pos->x = x + pos->x;
+ }
+ if (update_y)
+ {
+ if (!pos->y_null)
+ pos->y = y + pos->y;
+ }
+ if (update_z)
+ {
+ if (!pos->z_null)
+ pos->z = z + pos->z;
+ }
+ }
+ else
+ {
+
+ if (update_x)
+ {
+ pos->x_firmware_offset = pos->get_current_extruder().x_firmware_offset;
+ pos->x = x + pos->x_offset - pos->x_firmware_offset;
+ pos->x_null = false;
+ }
+ if (update_y)
+ {
+ pos->y_firmware_offset = pos->get_current_extruder().y_firmware_offset;
+ pos->y = y + pos->y_offset - pos->y_firmware_offset;
+ pos->y_null = false;
+ }
+ if (update_z)
+ {
+ pos->z_firmware_offset = pos->get_current_extruder().z_firmware_offset;
+ pos->z = z + pos->z_offset - pos->z_firmware_offset;
+ pos->z_null = false;
+ }
+ }
+ }
+
+ if (update_e)
+ {
+ if (!pos->is_extruder_relative_null)
+ {
+ if (pos->is_extruder_relative)
+ {
+ pos->get_current_extruder().e = e + pos->get_current_extruder().e;
+ }
+ else
+ {
+ pos->get_current_extruder().e = e + pos->get_current_extruder().e_offset;
+ }
+ }
+ }
+
+}
+
+void gcode_position::process_g0_g1(position* pos, parsed_command& cmd)
+{
+ bool update_x = false;
+ bool update_y = false;
+ bool update_z = false;
+ bool update_e = false;
+ bool update_f = false;
+ double x = 0;
+ double y = 0;
+ double z = 0;
+ double e = 0;
+ double f = 0;
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ const parsed_command_parameter p_cur_param = cmd.parameters[index];
+ if (p_cur_param.name == "X")
+ {
+ update_x = true;
+ x = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "Y")
+ {
+ update_y = true;
+ y = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "E")
+ {
+ update_e = true;
+ e = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "Z")
+ {
+ update_z = true;
+ z = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "F")
+ {
+ update_f = true;
+ f = p_cur_param.double_value;
+ }
+ }
+ update_position(pos, x, update_x, y, update_y, z, update_z, e, update_e, f, update_f, false, true);
+}
+
+void gcode_position::process_g2(position* pos, parsed_command& cmd)
+{
+ bool update_x = false;
+ bool update_y = false;
+ bool update_e = false;
+ bool update_f = false;
+ double x = 0;
+ double y = 0;
+ double e = 0;
+ double f = 0;
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ const parsed_command_parameter p_cur_param = cmd.parameters[index];
+ if (p_cur_param.name == "X")
+ {
+ update_x = true;
+ x = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "Y")
+ {
+ update_y = true;
+ y = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "E")
+ {
+ update_e = true;
+ e = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "F")
+ {
+ update_f = true;
+ f = p_cur_param.double_value;
+ }
+ }
+ update_position(pos, x, update_x, y, update_y, 0, false, e, update_e, f, update_f, false, true);
+}
+
+void gcode_position::process_g3(position* pos, parsed_command& cmd)
+{
+ return process_g2(pos, cmd);
+}
+
+void gcode_position::process_g10(position* pos, parsed_command& cmd)
+{
+ // Take 0 based extruder parameter in account
+ int p = 0;
+ bool has_p = false;
+ double x = 0;
+ bool has_x = false;
+ double y = 0;
+ bool has_y = false;
+ double z = 0;
+ bool has_z = false;
+ //double s = 0;
+ // Handle extruder offset commands
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p_cur_param = cmd.parameters[index];
+ /*if (p_cur_param.name == "S")
+ {
+ if (p_cur_param.value_type == 'F')
+ s = p_cur_param.double_value;
+ }
+ else */
+ if (p_cur_param.name == "P")
+ {
+ has_p = true;
+ if (p_cur_param.value_type == 'L')
+ {
+ p = static_cast(p_cur_param.unsigned_long_value);
+ }
+ else if (p_cur_param.value_type == 'F')
+ {
+ double val = p_cur_param.double_value;
+ val = val + 0.5 - (val < 0);
+ p = static_cast(val);
+ }
+ else
+ has_p = false;
+ }
+ else if (p_cur_param.name == "X")
+ {
+ has_x = true;
+ if (p_cur_param.value_type == 'F')
+ x = p_cur_param.double_value;
+ else
+ has_x = false;
+ }
+ else if (p_cur_param.name == "Y")
+ {
+ has_y = true;
+ if (p_cur_param.value_type == 'F')
+ y = p_cur_param.double_value;
+ else
+ has_y = false;
+ }
+ else if (p_cur_param.name == "Z")
+ {
+ has_z = true;
+ if (p_cur_param.value_type == 'F')
+ z = p_cur_param.double_value;
+ else
+ has_z = false;
+ }
+ }
+ // apply offsets
+ if (has_p)
+ {
+ // Take 0 based extruder parameter in account before setting offsets
+ if (!zero_based_extruder_)
+ {
+ p--;
+ }
+ if (p < 0)
+ {
+ p = 0;
+ }
+ else if (p > num_extruders_ - 1)
+ {
+ p = num_extruders_ - 1;
+ }
+ if (has_x)
+ pos->get_extruder(p).x_firmware_offset = x;
+ if (has_y)
+ pos->get_extruder(p).y_firmware_offset = y;
+ if (has_z)
+ pos->get_extruder(p).z_firmware_offset = z;
+ return;
+ }
+
+ // Todo: add firmware retract here
+}
+
+void gcode_position::process_g11(position* pos, parsed_command& cmd)
+{
+ // Todo: Fix G11
+}
+
+void gcode_position::process_g20(position* pos, parsed_command& cmd)
+{
+
+}
+
+void gcode_position::process_g21(position* pos, parsed_command& cmd)
+{
+
+}
+
+void gcode_position::process_g28(position* pos, parsed_command& cmd)
+{
+ bool has_x = false;
+ bool has_y = false;
+ bool has_z = false;
+ bool set_x_home = false;
+ bool set_y_home = false;
+ bool set_z_home = false;
+
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p_cur_param = cmd.parameters[index];
+ if (p_cur_param.name == "X")
+ has_x = true;
+ else if (p_cur_param.name == "Y")
+ has_y = true;
+ else if (p_cur_param.name == "Z")
+ has_z = true;
+ }
+ if (has_x)
+ {
+ pos->x_homed = true;
+ set_x_home = true;
+ }
+ if (has_y)
+ {
+ pos->y_homed = true;
+ set_y_home = true;
+ }
+ if (has_z)
+ {
+ pos->z_homed = true;
+ set_z_home = true;
+ }
+ if (!has_x && !has_y && !has_z)
+ {
+ pos->x_homed = true;
+ pos->y_homed = true;
+ pos->z_homed = true;
+ set_x_home = true;
+ set_y_home = true;
+ set_z_home = true;
+ }
+
+ if (set_x_home && !home_x_none_)
+ {
+ pos->x = home_x_;
+ pos->x_null = false;
+ }
+ // todo: set error flag on else
+ if (set_y_home && !home_y_none_)
+ {
+ pos->y = home_y_;
+ pos->y_null = false;
+ }
+ // todo: set error flag on else
+ if (set_z_home && !home_z_none_)
+ {
+ pos->z = home_z_;
+ pos->z_null = false;
+ }
+ // todo: set error flag on else
+}
+
+void gcode_position::process_g90(position* pos, parsed_command& cmd)
+{
+ // Set xyz to absolute mode
+ if (pos->is_relative_null)
+ pos->is_relative_null = false;
+
+ pos->is_relative = false;
+
+ if (g90_influences_extruder_)
+ {
+ // If g90/g91 influences the extruder, set the extruder to absolute mode too
+ if (pos->is_extruder_relative_null)
+ pos->is_extruder_relative_null = false;
+
+ pos->is_extruder_relative = false;
+ }
+
+}
+
+void gcode_position::process_g91(position* pos, parsed_command& cmd)
+{
+ // Set XYZ axis to relative mode
+ if (pos->is_relative_null)
+ pos->is_relative_null = false;
+
+ pos->is_relative = true;
+
+ if (g90_influences_extruder_)
+ {
+ // If g90/g91 influences the extruder, set the extruder to relative mode too
+ if (pos->is_extruder_relative_null)
+ pos->is_extruder_relative_null = false;
+
+ pos->is_extruder_relative = true;
+ }
+}
+
+void gcode_position::process_g92(position* pos, parsed_command& cmd)
+{
+ // Set position offset
+ bool update_x = false;
+ bool update_y = false;
+ bool update_z = false;
+ bool update_e = false;
+ bool o_exists = false;
+ double x = 0;
+ double y = 0;
+ double z = 0;
+ double e = 0;
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p_cur_param = cmd.parameters[index];
+ if (p_cur_param.name == "X")
+ {
+ update_x = true;
+ x = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "Y")
+ {
+ update_y = true;
+ y = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "E")
+ {
+ update_e = true;
+ e = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "Z")
+ {
+ update_z = true;
+ z = p_cur_param.double_value;
+ }
+ else if (p_cur_param.name == "O")
+ {
+ o_exists = true;
+ }
+ }
+
+ if (o_exists)
+ {
+ // Our fake O parameter exists, set axis to homed!
+ // This is a workaround to allow folks to use octolapse without homing (for shame, lol!)
+ pos->x_homed = true;
+ pos->y_homed = true;
+ pos->z_homed = true;
+ }
+
+ if (!o_exists && !update_x && !update_y && !update_z && !update_e)
+ {
+ if (!pos->x_null)
+ pos->x_offset = pos->x + pos->x_firmware_offset;
+ if (!pos->y_null)
+ pos->y_offset = pos->y + pos->y_firmware_offset;
+ if (!pos->z_null)
+ pos->z_offset = pos->z + pos->z_firmware_offset;
+ // Todo: Does this reset E too? Figure that $#$$ out Formerlurker!
+ pos->get_current_extruder().e_offset = pos->get_current_extruder().e;
+ }
+ else
+ {
+ if (update_x)
+ {
+ if (!pos->x_null && pos->x_homed)
+ pos->x_offset = pos->x - x + pos->x_firmware_offset;
+ else
+ {
+ pos->x = x;
+ pos->x_offset = 0;
+ pos->x_null = false;
+ }
+ }
+ if (update_y)
+ {
+ if (!pos->y_null && pos->y_homed)
+ pos->y_offset = pos->y - y + pos->y_firmware_offset;
+ else
+ {
+ pos->y = y;
+ pos->y_offset = 0;
+ pos->y_null = false;
+ }
+ }
+ if (update_z)
+ {
+ if (!pos->z_null && pos->z_homed)
+ pos->z_offset = pos->z - z + pos->z_firmware_offset;
+ else
+ {
+ pos->z = z;
+ pos->z_offset = 0;
+ pos->z_null = false;
+ }
+ }
+ if (update_e)
+ {
+ pos->get_current_extruder().e_offset = pos->get_current_extruder().e - e;
+ }
+ }
+}
+
+void gcode_position::process_m82(position* pos, parsed_command& cmd)
+{
+ // Set extrder mode to absolute
+ if (pos->is_extruder_relative_null)
+ pos->is_extruder_relative_null = false;
+
+ pos->is_extruder_relative = false;
+}
+
+void gcode_position::process_m83(position* pos, parsed_command& cmd)
+{
+ // Set extrder mode to relative
+ if (pos->is_extruder_relative_null)
+ pos->is_extruder_relative_null = false;
+
+ pos->is_extruder_relative = true;
+}
+
+void gcode_position::process_m207(position* pos, parsed_command& cmd)
+{
+ // Todo: impemente firmware retract
+}
+
+void gcode_position::process_m208(position* pos, parsed_command& cmd)
+{
+ // Todo: implement firmware retract
+}
+
+void gcode_position::process_m218(position* pos, parsed_command& cmd)
+{
+
+ // Set hotend offsets
+ int t = 0;
+ bool has_t = false;
+ double x = 0;
+ bool has_x = false;
+ double y = 0;
+ bool has_y = false;
+ double z = 0;
+ bool has_z = false;
+ // Handle extruder offset commands
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p_cur_param = cmd.parameters[index];
+
+ if (p_cur_param.name == "T")
+ {
+ has_t = true;
+ if (p_cur_param.value_type == 'L')
+ {
+ t = static_cast(p_cur_param.unsigned_long_value);
+ }
+ else if (p_cur_param.value_type == 'F')
+ {
+ double val = p_cur_param.double_value;
+ val = val + 0.5 - (val < 0);
+ t = static_cast(val);
+ }
+ else
+ has_t = false;
+
+ }
+ else if (p_cur_param.name == "X")
+ {
+ has_x = true;
+ if (p_cur_param.value_type == 'F')
+ x = p_cur_param.double_value;
+ else
+ has_x = false;
+ }
+ else if (p_cur_param.name == "Y")
+ {
+ has_y = true;
+ if (p_cur_param.value_type == 'F')
+ y = p_cur_param.double_value;
+ else
+ has_y = false;
+ }
+ else if (p_cur_param.name == "Z")
+ {
+ has_z = true;
+ if (p_cur_param.value_type == 'F')
+ z = p_cur_param.double_value;
+ else
+ has_z = false;
+ }
+ }
+ // apply offsets
+ if (has_t)
+ {
+ // Take 0 based extruder parameter in account before setting offsets
+ if (!zero_based_extruder_)
+ {
+ t--;
+ }
+ if (t < 0)
+ {
+ t = 0;
+ }
+ else if (t > num_extruders_ - 1)
+ {
+ t = num_extruders_ - 1;
+ }
+
+ if (has_x)
+ pos->get_extruder(t).x_firmware_offset = x;
+ if (has_y)
+ pos->get_extruder(t).y_firmware_offset = y;
+ if (has_z)
+ pos->get_extruder(t).z_firmware_offset = z;
+ return;
+ }
+}
+
+void gcode_position::process_m563(position* pos, parsed_command& cmd)
+{
+ // Todo: Work on this command, which defines tools and will affect which tool is selected.
+}
+
+void gcode_position::process_t(position* pos, parsed_command& cmd)
+{
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p_cur_param = cmd.parameters[index];
+ if (p_cur_param.name == "T" && p_cur_param.value_type == 'U')
+ {
+ pos->current_tool = static_cast(p_cur_param.unsigned_long_value);
+ if (!zero_based_extruder_)
+ {
+ pos->current_tool--;
+ }
+ if (pos->current_tool < 0)
+ {
+ pos->current_tool = 0;
+ }
+ else if (pos->current_tool > num_extruders_ - 1)
+ {
+ pos->current_tool = num_extruders_ - 1;
+ }
+
+ break;
+ }
+ }
+}
+
+gcode_comment_processor* gcode_position::get_gcode_comment_processor()
+{
+ return &comment_processor_;
+}
\ No newline at end of file
diff --git a/GcodeProcessorLib/gcode_position.h b/GcodeProcessorLib/gcode_position.h
new file mode 100644
index 0000000..e89f8df
--- /dev/null
+++ b/GcodeProcessorLib/gcode_position.h
@@ -0,0 +1,221 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Gcode Processor Library
+//
+// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GCODE_POSITION_H
+#define GCODE_POSITION_H
+#include
+#include
+#include