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

github.com/FormerLurker/ArcWelderLib.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFormerLurker <hochgebe@gmail.com>2020-04-26 01:20:39 +0300
committerFormerLurker <hochgebe@gmail.com>2020-04-26 01:20:39 +0300
commit4fcf89d4995921b89b579d06052df11b66e4879f (patch)
tree390bbfcab87591e802f9ac91ac2d531b4ae53946
Add project files.
-rw-r--r--.gitattributes63
-rw-r--r--.gitignore340
-rw-r--r--ArcWelder.sln81
-rw-r--r--ArcWelder/ArcWelder.vcxproj158
-rw-r--r--ArcWelder/ArcWelder.vcxproj.filters42
-rw-r--r--ArcWelder/arc_welder.cpp745
-rw-r--r--ArcWelder/arc_welder.h99
-rw-r--r--ArcWelder/segmented_arc.cpp431
-rw-r--r--ArcWelder/segmented_arc.h58
-rw-r--r--ArcWelder/segmented_shape.cpp422
-rw-r--r--ArcWelder/segmented_shape.h192
-rw-r--r--ArcWelder/unwritten_command.h62
-rw-r--r--ArcWelderConsole/ArcWelderConsole.cpp236
-rw-r--r--ArcWelderConsole/ArcWelderConsole.h26
-rw-r--r--ArcWelderConsole/ArcWelderConsole.vcxproj161
-rw-r--r--ArcWelderConsole/ArcWelderConsole.vcxproj.filters27
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp42
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.h52
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj163
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters33
-rw-r--r--ArcWelderInverseProcessor/inverse_processor.cpp467
-rw-r--r--ArcWelderInverseProcessor/inverse_processor.h64
-rw-r--r--ArcWelderTest/ArcWelderTest.cpp268
-rw-r--r--ArcWelderTest/ArcWelderTest.h72
-rw-r--r--ArcWelderTest/ArcWelderTest.vcxproj161
-rw-r--r--ArcWelderTest/ArcWelderTest.vcxproj.filters27
-rw-r--r--GcodeProcessorLib/GcodeProcessorLib.vcxproj169
-rw-r--r--GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters87
-rw-r--r--GcodeProcessorLib/array_list.cpp22
-rw-r--r--GcodeProcessorLib/array_list.h163
-rw-r--r--GcodeProcessorLib/circular_buffer.cpp22
-rw-r--r--GcodeProcessorLib/circular_buffer.h117
-rw-r--r--GcodeProcessorLib/extruder.cpp53
-rw-r--r--GcodeProcessorLib/extruder.h49
-rw-r--r--GcodeProcessorLib/gcode_comment_processor.cpp310
-rw-r--r--GcodeProcessorLib/gcode_comment_processor.h90
-rw-r--r--GcodeProcessorLib/gcode_parser.cpp676
-rw-r--r--GcodeProcessorLib/gcode_parser.h56
-rw-r--r--GcodeProcessorLib/gcode_position.cpp1418
-rw-r--r--GcodeProcessorLib/gcode_position.h221
-rw-r--r--GcodeProcessorLib/logger.cpp149
-rw-r--r--GcodeProcessorLib/logger.h64
-rw-r--r--GcodeProcessorLib/parsed_command.cpp104
-rw-r--r--GcodeProcessorLib/parsed_command.h44
-rw-r--r--GcodeProcessorLib/parsed_command_parameter.cpp48
-rw-r--r--GcodeProcessorLib/parsed_command_parameter.h41
-rw-r--r--GcodeProcessorLib/position.cpp434
-rw-r--r--GcodeProcessorLib/position.h106
-rw-r--r--GcodeProcessorLib/utilities.cpp173
-rw-r--r--GcodeProcessorLib/utilities.h57
-rw-r--r--PyArcWelder/PyArcWelder.vcxproj171
-rw-r--r--PyArcWelder/PyArcWelder.vcxproj.filters45
-rw-r--r--PyArcWelder/py_arc_welder.cpp48
-rw-r--r--PyArcWelder/py_arc_welder.h49
-rw-r--r--PyArcWelder/py_arc_welder_extension.cpp281
-rw-r--r--PyArcWelder/py_arc_welder_extension.h78
-rw-r--r--PyArcWelder/py_logger.cpp246
-rw-r--r--PyArcWelder/py_logger.h66
-rw-r--r--PyArcWelder/python_helpers.cpp116
-rw-r--r--PyArcWelder/python_helpers.h40
60 files changed, 10305 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}</ProjectGuid>
+ <RootNamespace>ArcWelder</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="arc_welder.h" />
+ <ClInclude Include="segmented_arc.h" />
+ <ClInclude Include="segmented_shape.h" />
+ <ClInclude Include="unwritten_command.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="arc_welder.cpp" />
+ <ClCompile Include="segmented_arc.cpp" />
+ <ClCompile Include="segmented_shape.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="arc_welder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="segmented_arc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="segmented_shape.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="unwritten_command.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="arc_welder.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="segmented_arc.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="segmented_shape.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ 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 <time.h>
+#include <vector>
+#include <sstream>
+#include "utilities.h"
+#include <iostream>
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+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<std::string> 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<std::string>();
+ args.is_bound_ = false;
+ args.is_circular_bed = false;
+ args.x_min = -9999;
+ args.x_max = 9999;
+ args.y_min = -9999;
+ args.y_max = 9999;
+ args.z_min = -9999;
+ args.z_max = 9999;
+ return args;
+}
+
+arc_welder::~arc_welder()
+{
+ delete p_source_position_;
+}
+
+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<double>(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<long>(gcodeFile.tellg());
+ // ToDo: tellg does not do what I think it does, but why?
+ long bytesRemaining = file_size_ - file_position;
+ double percentProgress = static_cast<double>(file_position) / static_cast<double>(file_size_) * 100.0;
+ double secondsElapsed = get_time_elapsed(start_clock, clock());
+ double bytesPerSecond = static_cast<double>(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<double>(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 <string>
+#include <vector>
+#include <set>
+#include "gcode_position.h"
+#include "position.h"
+#include "gcode_parser.h"
+#include "segmented_arc.h"
+#include <iostream>
+#include <fstream>
+#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_command> unwritten_commands_;
+ array_list<parsed_command> 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<std::string> 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 <iostream>
+#include <iomanip>
+#include <sstream>
+#include "math.h"
+#include <stdio.h>
+
+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 <iomanip>
+#include <sstream>
+
+#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 <math.h>
+#include <iostream>
+#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 <string>
+#include <limits>
+#define PI_DOUBLE 3.14159265358979323846
+#define CIRCLE_FLOATING_POINT_TOLERANCE 0.0000000001
+#include <list>
+#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<point> 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 <iostream>
+#include <sstream>
+#include <iomanip>
+#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 <std::string> 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<std::string> log_names;
+ log_names.push_back("arc_welder.gcode_conversion");
+ std::vector<int> 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{F4910B67-FE16-40EA-9BD5-91017C569B0D}</ProjectGuid>
+ <RootNamespace>ArcWelderConsole</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="ArcWelderConsole.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="ArcWelderConsole.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\ArcWelder\ArcWelder.vcxproj">
+ <Project>{1a4dbab1-bb42-4db1-b168-f113784efcef}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\GcodeProcessorLib\GcodeProcessorLib.vcxproj">
+ <Project>{31478bae-104b-4cc3-9876-42fa90cbd5fe}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="ArcWelderConsole.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="ArcWelderConsole.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ 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 <iostream>
+#include <sstream>
+#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 <string>
+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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{9C40BB30-5186-4181-94D6-AC8DFE361A5A}</ProjectGuid>
+ <RootNamespace>ArcWelderInverseProcessor</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="ArcWelderInverseProcessor.h" />
+ <ClInclude Include="inverse_processor.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="ArcWelderInverseProcessor.cpp" />
+ <ClCompile Include="inverse_processor.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\ArcWelder\ArcWelder.vcxproj">
+ <Project>{1a4dbab1-bb42-4db1-b168-f113784efcef}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\GcodeProcessorLib\GcodeProcessorLib.vcxproj">
+ <Project>{31478bae-104b-4cc3-9876-42fa90cbd5fe}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="ArcWelderInverseProcessor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="inverse_processor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="ArcWelderInverseProcessor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="inverse_processor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "inverse_processor.h"
+#include "math.h"
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <fstream>
+
+//#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<std::string>();
+ args.is_bound_ = false;
+ args.is_circular_bed = false;
+ args.x_min = -9999;
+ args.x_max = 9999;
+ args.y_min = -9999;
+ args.y_max = 9999;
+ args.z_min = -9999;
+ args.z_max = 9999;
+ return args;
+}
+
+inverse_processor::~inverse_processor()
+{
+ delete p_source_position_;
+}
+
+void inverse_processor::process()
+{
+ // Create a stringstream we can use for messaging.
+ std::stringstream stream;
+
+ int read_lines_before_clock_check = 5000;
+ //std::cout << "stabilization::process_file - Processing file.\r\n";
+ stream << "Decompressing gcode file.";
+ stream << "Source File: " << source_path_ << "\n";
+ stream << "Target File: " << target_path_ << "\n";
+ std::cout << stream.str();
+ const clock_t start_clock = clock();
+
+ // Create the source file read stream and target write stream
+ std::ifstream gcode_file;
+ 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<float>(p_pre_pos->get_gcode_x());
+ position[Y_AXIS] = static_cast<float>(p_pre_pos->get_gcode_y());
+ position[Z_AXIS] = static_cast<float>(p_pre_pos->get_gcode_z());
+ position[E_AXIS] = static_cast<float>(p_pre_pos->get_current_extruder().get_offset_e());
+ float target[4];
+ target[X_AXIS] = static_cast<float>(p_cur_pos->get_gcode_x());
+ target[Y_AXIS] = static_cast<float>(p_cur_pos->get_gcode_y());
+ target[Z_AXIS] = static_cast<float>(p_cur_pos->get_gcode_z());
+ target[E_AXIS] = static_cast<float>(p_cur_pos->get_current_extruder().get_offset_e());
+ float offset[2];
+ offset[0] = 0.0;
+ offset[1] = 0.0;
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p = cmd.parameters[index];
+ if (p.name == "I")
+ {
+ offset[0] = static_cast<float>(p.double_value);
+ }
+ else if (p.name == "J")
+ {
+ offset[1] = static_cast<float>(p.double_value);
+ }
+ }
+ float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc
+ uint8_t isclockwise = cmd.command == "G2" ? 1 : 0;
+ output_file << mc_arc(position, target, offset, X_AXIS, Y_AXIS, Z_AXIS, static_cast<float>(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<double>(end_clock) - static_cast<double>(start_clock)) / CLOCKS_PER_SEC;
+
+ stream.clear();
+ stream.str("");
+ stream << "Completed file processing\r\n";
+ stream << "\tLines Processed : " << lines_processed << "\r\n";
+ stream << "\tTotal Seconds : " << total_seconds << "\r\n";
+ stream << "\tExtra Trig Count : " << trig_calc_count << "\r\n";
+ stream << "\tTotal E Adjustment : " << total_e_adjustment << "\r\n";
+ std::cout << stream.str();
+}
+
+// The arc is approximated by generating a huge number of tiny, linear segments. The length of each
+// segment is configured in settings.mm_per_arc_segment.
+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<uint16_t>(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 <string>
+#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 <iostream>
+
+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<double> 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<std::string>();
+ 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<std::string>();
+ 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<std::string>();
+ 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<std::string> logger_names;
+ logger_names.push_back("arc_welder.gcode_conversion");
+ std::vector<int> 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<progress_callback>(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 <stdlib.h>
+#include <crtdbg.h>
+#include <assert.h>
+#include <iostream>
+#include <fstream>
+#include <iomanip>
+#include <string>
+#include <vector>
+#include <chrono>
+#include "gcode_position.h"
+#include "gcode_parser.h"
+#include <sstream>
+#include "arc_welder.h"
+#include "array_list.h"
+#include "logger.h"
+#include <exception>
+
+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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{18D7E538-6ACE-44E4-B83E-31C3E44D4227}</ProjectGuid>
+ <RootNamespace>ArcWelderTest</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="ArcWelderTest.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="ArcWelderTest.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\ArcWelder\ArcWelder.vcxproj">
+ <Project>{1a4dbab1-bb42-4db1-b168-f113784efcef}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\GcodeProcessorLib\GcodeProcessorLib.vcxproj">
+ <Project>{31478bae-104b-4cc3-9876-42fa90cbd5fe}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="ArcWelderTest.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="ArcWelderTest.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{31478BAE-104B-4CC3-9876-42FA90CBD5FE}</ProjectGuid>
+ <RootNamespace>GcodeProcessorLib</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="array_list.h" />
+ <ClInclude Include="circular_buffer.h" />
+ <ClInclude Include="extruder.h" />
+ <ClInclude Include="gcode_comment_processor.h" />
+ <ClInclude Include="gcode_parser.h" />
+ <ClInclude Include="gcode_position.h" />
+ <ClInclude Include="logger.h" />
+ <ClInclude Include="parsed_command.h" />
+ <ClInclude Include="parsed_command_parameter.h" />
+ <ClInclude Include="position.h" />
+ <ClInclude Include="utilities.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="array_list.cpp" />
+ <ClCompile Include="circular_buffer.cpp" />
+ <ClCompile Include="extruder.cpp" />
+ <ClCompile Include="gcode_comment_processor.cpp" />
+ <ClCompile Include="gcode_parser.cpp" />
+ <ClCompile Include="gcode_position.cpp" />
+ <ClCompile Include="logger.cpp" />
+ <ClCompile Include="parsed_command.cpp" />
+ <ClCompile Include="parsed_command_parameter.cpp" />
+ <ClCompile Include="position.cpp" />
+ <ClCompile Include="utilities.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="array_list.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="circular_buffer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="extruder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="gcode_comment_processor.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="gcode_parser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="gcode_position.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="logger.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="parsed_command.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="parsed_command_parameter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="position.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="utilities.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="array_list.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="circular_buffer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="extruder.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="gcode_comment_processor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="gcode_parser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="gcode_position.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="logger.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="parsed_command.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="parsed_command_parameter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="position.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="utilities.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ 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 <exception>
+template <typename T>
+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<T>& 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 <exception>
+template <typename T>
+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<T>& 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 <iostream>
+
+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 <string>
+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 <cmath>
+#include <iostream>
+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<std::string> text_only_function_names = { "M117" }; // "M117" is an example of a command that would work here.
+
+ std::vector<std::string> 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<std::string> text_only_function_names;
+ text_only_function_names.push_back(("M117"));
+ // parsable_command_names
+ std::vector<std::string> 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<char *>(gcode);
+ char * p = const_cast<char *>(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, &param))
+ 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, &param))
+ {
+ 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, &param))
+ 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<unsigned int>((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 <string>
+#include <vector>
+#include <set>
+#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<std::string> text_only_functions_;
+ std::set<std::string> 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 <algorithm>
+#include <iterator>
+#include <math.h>
+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<std::string> 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<std::string> 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<std::string, gcode_position::pos_function_type> gcode_position::get_gcode_functions()
+{
+ std::map<std::string, pos_function_type> 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<int>(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<int>(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<int>(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<int>(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<int>(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 <string>
+#include <vector>
+#include <map>
+#include "gcode_parser.h"
+#include "position.h"
+#include "gcode_comment_processor.h"
+struct gcode_position_args {
+ gcode_position_args() {
+ position_buffer_size = 50;
+ // Wipe Variables
+ shared_extruder = true;
+ autodetect_position = true;
+ is_circular_bed = false;
+ home_x = 0;
+ home_y = 0;
+ home_z = 0;
+ home_x_none = false;
+ home_y_none = false;
+ home_z_none = false;
+ retraction_lengths = NULL;
+ z_lift_heights = NULL;
+ x_firmware_offsets = NULL;
+ y_firmware_offsets = NULL;
+ priming_height = 0;
+ minimum_layer_height = 0;
+ height_increment = 0;
+ g90_influences_extruder = false;
+ xyz_axis_default_mode = "absolute";
+ e_axis_default_mode = "absolute";
+ units_default = "millimeters";
+ is_bound_ = false;
+ x_min = 0;
+ x_max = 0;
+ y_min = 0;
+ y_max = 0;
+ z_min = 0;
+ z_max = 0;
+ snapshot_x_min = 0;
+ snapshot_x_max = 0;
+ snapshot_y_min = 0;
+ snapshot_y_max = 0;
+ snapshot_z_min = 0;
+ snapshot_z_max = 0;
+ num_extruders = 1;
+ default_extruder = 0;
+ zero_based_extruder = true;
+ std::vector<std::string> location_detection_commands; // Final list of location detection commands
+ set_num_extruders(num_extruders);
+ }
+ gcode_position_args(const gcode_position_args &pos); // Copy Constructor
+ ~gcode_position_args()
+ {
+ delete_retraction_lengths();
+ delete_z_lift_heights();
+ delete_x_firmware_offsets();
+ delete_y_firmware_offsets();
+ }
+ int position_buffer_size;
+ bool autodetect_position;
+ bool is_circular_bed;
+ // Wipe variables
+ double home_x;
+ double home_y;
+ double home_z;
+ bool home_x_none;
+ bool home_y_none;
+ bool home_z_none;
+ double* retraction_lengths;
+ double* z_lift_heights;
+ double* x_firmware_offsets;
+ double* y_firmware_offsets;
+ double priming_height;
+ double minimum_layer_height;
+ double height_increment;
+ bool g90_influences_extruder;
+ bool is_bound_;
+ double snapshot_x_min;
+ double snapshot_x_max;
+ double snapshot_y_min;
+ double snapshot_y_max;
+ double snapshot_z_min;
+ double snapshot_z_max;
+ double x_min;
+ double x_max;
+ double y_min;
+ double y_max;
+ double z_min;
+ double z_max;
+ bool shared_extruder;
+ bool zero_based_extruder;
+ int num_extruders;
+ int default_extruder;
+ std::string xyz_axis_default_mode;
+ std::string e_axis_default_mode;
+ std::string units_default;
+ std::vector<std::string> location_detection_commands; // Final list of location detection commands
+ gcode_position_args& operator=(const gcode_position_args& pos_args);
+ void set_num_extruders(int num_extruders);
+ void delete_retraction_lengths();
+ void delete_z_lift_heights();
+ void delete_x_firmware_offsets();
+ void delete_y_firmware_offsets();
+};
+
+class gcode_position
+{
+public:
+ typedef void(gcode_position::*pos_function_type)(position*, parsed_command&);
+ gcode_position(gcode_position_args args);
+ gcode_position();
+ virtual ~gcode_position();
+
+ void update(parsed_command &command, long file_line_number, long gcode_number, const long file_position);
+ void update_position(position *position, double x, bool update_x, double y, bool update_y, double z, bool update_z, double e, bool update_e, double f, bool update_f, bool force, bool is_g1_g0) const;
+ void undo_update();
+ position * undo_update(int num_updates);
+ int get_num_positions();
+ position get_position(int index);
+ position get_current_position();
+ position get_previous_position();
+ position * get_position_ptr(int index);
+ position * get_current_position_ptr();
+ position * get_previous_position_ptr();
+ gcode_comment_processor* get_gcode_comment_processor();
+private:
+ gcode_position(const gcode_position &source);
+ int position_buffer_size_;
+ position* positions_;
+ int cur_pos_;
+ int num_pos_;
+ void add_position(parsed_command &);
+ void add_position(position &);
+ bool autodetect_position_;
+ double priming_height_;
+ double home_x_;
+ double home_y_;
+ double home_z_;
+ bool home_x_none_;
+ bool home_y_none_;
+ bool home_z_none_;
+ double * retraction_lengths_;
+ double * z_lift_heights_;
+ double minimum_layer_height_;
+ double height_increment_;
+ bool g90_influences_extruder_;
+ std::string e_axis_default_mode_;
+ std::string xyz_axis_default_mode_;
+ std::string units_default_;
+ bool is_bound_;
+ double x_min_;
+ double x_max_;
+ double y_min_;
+ double y_max_;
+ double z_min_;
+ double z_max_;
+ bool is_circular_bed_;
+ double snapshot_x_min_;
+ double snapshot_x_max_;
+ double snapshot_y_min_;
+ double snapshot_y_max_;
+ double snapshot_z_min_;
+ double snapshot_z_max_;
+ int num_extruders_;
+ bool shared_extruder_;
+ bool zero_based_extruder_;
+
+ std::map<std::string, pos_function_type> gcode_functions_;
+ std::map<std::string, pos_function_type>::iterator gcode_functions_iterator_;
+
+ std::map<std::string, pos_function_type> get_gcode_functions();
+ /// Process Gcode Command Functions
+ void process_g0_g1(position*, parsed_command&);
+ void process_g2(position*, parsed_command&);
+ void process_g3(position*, parsed_command&);
+ void process_g10(position*, parsed_command&);
+ void process_g11(position*, parsed_command&);
+ void process_g20(position*, parsed_command&);
+ void process_g21(position*, parsed_command&);
+ void process_g28(position*, parsed_command&);
+ void process_g90(position*, parsed_command&);
+ void process_g91(position*, parsed_command&);
+ void process_g92(position*, parsed_command&);
+ void process_m82(position*, parsed_command&);
+ void process_m83(position*, parsed_command&);
+ void process_m207(position*, parsed_command&);
+ void process_m208(position*, parsed_command&);
+ void process_m218(position*, parsed_command&);
+ void process_m563(position*, parsed_command&);
+ void process_t(position*, parsed_command&);
+
+ gcode_comment_processor comment_processor_;
+ void delete_retraction_lengths_();
+ void delete_z_lift_heights_();
+ void set_num_extruders(int num_extruders);
+};
+
+#endif
diff --git a/GcodeProcessorLib/logger.cpp b/GcodeProcessorLib/logger.cpp
new file mode 100644
index 0000000..a556b3f
--- /dev/null
+++ b/GcodeProcessorLib/logger.cpp
@@ -0,0 +1,149 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 "logger.h"
+logger::logger(std::vector<std::string> names, std::vector<int> levels) {
+ // set to true by default, but can be changed by inheritance to support mandatory innitialization (for python or other integrations)
+ loggers_created_ = true;
+ num_loggers_ = names.size();
+ logger_names_ = new std::string[static_cast<int>(num_loggers_)];
+ logger_levels_ = new int[static_cast<int>(num_loggers_)];
+ // this is slow due to the vectors, but it is trivial. Could switch to an iterator
+ for (int index = 0; index < num_loggers_; index++)
+ {
+ logger_names_[index] = names[index];
+ logger_levels_[index] = levels[index];
+ }
+
+ set_log_level_by_value(NOSET);
+}
+
+logger::~logger() {
+ delete[] logger_names_;
+ delete[] logger_levels_;
+}
+
+void logger::set_log_level_by_value(const int logger_type, const int level_value)
+{
+ logger_levels_[logger_type] = get_log_level_for_value(level_value);
+}
+void logger::set_log_level_by_value(const int level_value)
+{
+ for (int type_index = 0; type_index < num_loggers_; type_index++)
+ {
+ logger_levels_[type_index] = get_log_level_for_value(level_value);
+ }
+}
+void logger::set_log_level(const int logger_type, const int log_level)
+{
+ logger_levels_[logger_type] = log_level;
+}
+
+void logger::set_log_level(const int log_level)
+{
+ for (int type_index = 0; type_index < num_loggers_; type_index++)
+ {
+ logger_levels_[type_index] = log_level;
+ }
+}
+
+int logger::get_log_level_value(const int log_level)
+{
+ return log_level_values[log_level];
+}
+int logger::get_log_level_for_value(int log_level_value)
+{
+ for (int log_level = 0; log_level < LOG_LEVEL_COUNT; log_level++)
+ {
+ if (log_level_values[log_level] == log_level_value)
+ return log_level;
+ }
+ return 0;
+}
+bool logger::is_log_level_enabled(const int logger_type, const int log_level)
+{
+ return logger_levels_[logger_type] <= log_level;
+}
+
+void logger::create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output)
+{
+ // example message
+ // 2020-04-20 21:36:59,414 - arc_welder.__init__ - INFO - MESSAGE_GOES_HERE
+
+ // Create the time string in YYYY-MM-DD HH:MM:SS format
+ std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
+ std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
+ const time_t now_time = std::chrono::system_clock::to_time_t(now);
+ struct tm tstruct;
+ char buf[25];
+ tstruct = *localtime(&now_time);
+ // DOESN'T WORK WITH ALL COMPILERS...
+ //localtime_s(&tstruct, &now_time);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S.", &tstruct);
+ output = buf;
+ std::string s_miliseconds = std::to_string(ms.count());
+ // Add the milliseconds, padded with 0s, to the output
+ output.append(std::string(3 - s_miliseconds.length(), '0') + s_miliseconds);
+ // Add a spacer
+ output.append(" - ");
+ // Add the logger name
+ output.append(logger_names_[logger_type]);
+ // add a spacer
+ output.append(" - ");
+ // add the log level name
+ output.append(log_level_names[log_level]);
+ // add a spacer
+ output.append(" - ");
+ // add the message
+ output.append(message);
+}
+
+void logger::log_exception(const int logger_type, const std::string& message)
+{
+ log(logger_type, log_levels::ERROR, message, true);
+}
+
+void logger::log(const int logger_type, const int log_level, const std::string& message)
+{
+ log(logger_type, log_level, message, false);
+}
+
+void logger::log(const int logger_type, const int log_level, const std::string& message, bool is_exception)
+{
+ // Make sure the loggers have been initialized
+ if (!loggers_created_)
+ return;
+ // Make sure the current logger is enabled for the log_level
+ if (!is_log_level_enabled(logger_type, log_level))
+ return;
+
+ // create the log message
+ std::string output;
+ create_log_message(logger_type, log_level, message, output);
+
+ // write the log
+ if (is_exception)
+ std::cerr << output << "\n";
+ else
+ std::cout << output << "\n";
+
+}
diff --git a/GcodeProcessorLib/logger.h b/GcodeProcessorLib/logger.h
new file mode 100644
index 0000000..c681674
--- /dev/null
+++ b/GcodeProcessorLib/logger.h
@@ -0,0 +1,64 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 <string>
+#include <iostream>
+#include <vector>
+#include <cstdarg>
+#include <stdio.h>
+#include <chrono>
+#include <array>
+
+#define LOG_LEVEL_COUNT 7
+enum log_levels { NOSET, VERBOSE, DEBUG, INFO, WARNING , ERROR, CRITICAL};
+const std::array<std::string, 7> log_level_names = { {"NOSET", "VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} };
+const static int log_level_values[LOG_LEVEL_COUNT] = { 0, 5, 10, 20, 30, 40, 50};
+
+class logger
+{
+public:
+ logger(std::vector<std::string> names, std::vector<int> levels);
+ virtual ~logger();
+
+ void set_log_level_by_value(const int logger_type, const int log_level_value);
+ void set_log_level_by_value(const int log_level_value);
+
+ void set_log_level(const int logger_type, const int log_level);
+ void set_log_level(const int log_level);
+
+ virtual void log(const int logger_type, const int log_level, const std::string& message);
+ virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception);
+ virtual void log_exception(const int logger_type, const std::string& message);
+ static int get_log_level_value(const int log_level);
+ static int get_log_level_for_value(int log_level_value);
+ virtual bool is_log_level_enabled(const int logger_type, const int log_level);
+protected:
+ virtual void create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output);
+
+ bool loggers_created_;
+private:
+ std::string* logger_names_;
+ int * logger_levels_;
+ int num_loggers_;
+
+};
+
diff --git a/GcodeProcessorLib/parsed_command.cpp b/GcodeProcessorLib/parsed_command.cpp
new file mode 100644
index 0000000..5cd00d9
--- /dev/null
+++ b/GcodeProcessorLib/parsed_command.cpp
@@ -0,0 +1,104 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 "parsed_command.h"
+#include <sstream>
+#include <iomanip>
+#include <stdlib.h>
+parsed_command::parsed_command()
+{
+
+ command.reserve(8);
+ gcode.reserve(128);
+ comment.reserve(128);
+ parameters.reserve(6);
+ is_known_command = false;
+ is_empty = true;
+}
+
+void parsed_command::clear()
+{
+
+ command.clear();
+ gcode.clear();
+ comment.clear();
+ parameters.clear();
+ is_known_command = false;
+ is_empty = true;
+}
+
+std::string parsed_command::rewrite_gcode_string()
+{
+ std::stringstream stream;
+
+ // add command
+ stream << command;
+ if (parameters.size() > 0)
+ {
+ for (unsigned int index = 0; index < parameters.size(); index++)
+ {
+ parsed_command_parameter p = parameters[index];
+
+ if (p.name == "E")
+ {
+ stream << std::fixed << std::setprecision(5);
+ }
+ else if (p.name == "F")
+ {
+ stream << std::fixed << std::setprecision(0);
+ }
+ else
+ {
+ stream << std::fixed << std::setprecision(3);
+ }
+
+ stream << " " << p.name;
+ switch (p.value_type)
+ {
+ case 'S':
+ stream << p.string_value;
+ break;
+ case 'F':
+ stream << p.double_value;
+ break;
+ case 'U':
+ stream << p.unsigned_long_value;
+ break;
+ }
+ }
+ }
+ if (comment.size() > 0)
+ {
+ stream << ";" << comment;
+ }
+ return stream.str();
+}
+
+std::string parsed_command::to_string()
+{
+ if (comment.size() > 0)
+ {
+ return gcode + ";" + comment;
+ }
+ return gcode;
+}
+
diff --git a/GcodeProcessorLib/parsed_command.h b/GcodeProcessorLib/parsed_command.h
new file mode 100644
index 0000000..b10d5bb
--- /dev/null
+++ b/GcodeProcessorLib/parsed_command.h
@@ -0,0 +1,44 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 PARSED_COMMAND_H
+#define PARSED_COMMAND_H
+#include <string>
+#include <vector>
+#include "parsed_command_parameter.h"
+
+struct parsed_command
+{
+public:
+ parsed_command();
+ std::string command;
+ std::string gcode;
+ std::string comment;
+ bool is_empty;
+ bool is_known_command;
+ std::vector<parsed_command_parameter> parameters;
+ void clear();
+ std::string to_string();
+ std::string rewrite_gcode_string();
+};
+
+#endif \ No newline at end of file
diff --git a/GcodeProcessorLib/parsed_command_parameter.cpp b/GcodeProcessorLib/parsed_command_parameter.cpp
new file mode 100644
index 0000000..fe4ca8b
--- /dev/null
+++ b/GcodeProcessorLib/parsed_command_parameter.cpp
@@ -0,0 +1,48 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 "parsed_command_parameter.h"
+#include "parsed_command.h"
+parsed_command_parameter::parsed_command_parameter()
+{
+ value_type = 'N';
+ name.reserve(1);
+}
+
+parsed_command_parameter::parsed_command_parameter(const std::string name, double value) : name(name), double_value(value)
+{
+ value_type = 'F';
+}
+
+parsed_command_parameter::parsed_command_parameter(const std::string name, const std::string value) : name(name), string_value(value)
+{
+ value_type = 'S';
+}
+
+parsed_command_parameter::parsed_command_parameter(const std::string name, const unsigned long value) : name(name), unsigned_long_value(value)
+{
+ value_type = 'U';
+}
+parsed_command_parameter::~parsed_command_parameter()
+{
+
+}
diff --git a/GcodeProcessorLib/parsed_command_parameter.h b/GcodeProcessorLib/parsed_command_parameter.h
new file mode 100644
index 0000000..04a7f06
--- /dev/null
+++ b/GcodeProcessorLib/parsed_command_parameter.h
@@ -0,0 +1,41 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 PARSED_COMMAND_PARAMETER_H
+#define PARSED_COMMAND_PARAMETER_H
+#include <string>
+struct parsed_command_parameter
+{
+public:
+ parsed_command_parameter();
+ ~parsed_command_parameter();
+ parsed_command_parameter(std::string name, double value);
+ parsed_command_parameter(std::string name, std::string value);
+ parsed_command_parameter(std::string name, unsigned long value);
+ std::string name;
+ char value_type;
+ double double_value;
+ unsigned long unsigned_long_value;
+ std::string string_value;
+};
+
+#endif
diff --git a/GcodeProcessorLib/position.cpp b/GcodeProcessorLib/position.cpp
new file mode 100644
index 0000000..6a85801
--- /dev/null
+++ b/GcodeProcessorLib/position.cpp
@@ -0,0 +1,434 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 "position.h"
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+std::string position::to_string(bool rewrite, bool verbose, std::string additional_comment)
+{
+ if (verbose)
+ {
+ std::stringstream stream;
+ stream << std::fixed << std::setprecision(5);
+ stream << "," << get_current_extruder().e << "," << get_current_extruder().get_offset_e() << "," << get_current_extruder().e_relative;
+ command.comment.append(stream.str());
+ }
+ command.comment.append(additional_comment);
+
+ if (rewrite)
+ {
+ return command.rewrite_gcode_string();
+ }
+
+ return command.to_string();
+}
+
+void position::set_xyz_axis_mode(const std::string& xyz_axis_default_mode)
+{
+ if (xyz_axis_default_mode == "relative" || xyz_axis_default_mode == "force-relative")
+ {
+ is_relative = true;
+ is_relative_null = false;
+ }
+ else if (xyz_axis_default_mode == "absolute" || xyz_axis_default_mode == "force-absolute")
+ {
+ is_relative = false;
+ is_relative_null = false;
+ }
+
+
+}
+
+void position::set_e_axis_mode(const std::string& e_axis_default_mode)
+{
+ if (e_axis_default_mode == "relative" || e_axis_default_mode == "force-relative")
+ {
+ is_extruder_relative = true;
+ is_extruder_relative_null = false;
+ }
+ else if (e_axis_default_mode == "absolute" || e_axis_default_mode == "force-absolute")
+ {
+ is_extruder_relative = false;
+ is_extruder_relative_null = false;
+ }
+
+
+}
+
+void position::set_units_default(const std::string& units_default)
+{
+ if (units_default == "inches")
+ {
+ is_metric = false;
+ is_metric_null = false;
+ }
+ else if (units_default == "millimeters")
+ {
+ is_metric = true;
+ is_metric_null = false;
+ }
+}
+
+bool position::can_take_snapshot()
+{
+ return (
+ !is_relative_null &&
+ !is_extruder_relative_null &&
+ has_definite_position &&
+ is_printer_primed &&
+ !is_metric_null
+ );
+}
+
+position::position()
+{
+ has_been_deleted = false;
+ is_empty = true;
+ feature_type_tag = 0;
+ f = 0;
+ f_null = true;
+ x = 0;
+ x_null = true;
+ x_offset = 0;
+ x_firmware_offset = 0;
+ x_homed = false;
+ y = 0;
+ y_null = true;
+ y_offset = 0;
+ y_firmware_offset = 0;
+ y_homed = false;
+ z = 0;
+ z_null = true;
+ z_offset = 0;
+ z_firmware_offset = 0;
+ z_homed = false;
+ is_relative = false;
+ is_relative_null = true;
+ is_extruder_relative = false;
+ is_extruder_relative_null = true;
+ is_metric = true;
+ is_metric_null = true;
+ last_extrusion_height = 0;
+ last_extrusion_height_null = true;
+ layer = 0;
+ height = 0;
+ height_increment = 0;
+ height_increment_change_count = 0;
+ is_printer_primed = false;
+ has_definite_position = false;
+ z_relative = 0;
+ is_in_position = false;
+ in_path_position = false;
+ is_zhop = false;
+ is_layer_change = false;
+ is_height_change = false;
+ is_height_increment_change = false;
+ is_xy_travel = false;
+ is_xyz_travel = false;
+ has_xy_position_changed = false;
+ has_position_changed = false;
+ has_received_home_command = false;
+ file_line_number = -1;
+ gcode_number = -1;
+ file_position = -1;
+ gcode_ignored = true;
+ is_in_bounds = true;
+ current_tool = -1;
+ p_extruders = NULL;
+ num_extruders = 0;
+ set_num_extruders(num_extruders);
+}
+
+position::position(int extruder_count)
+{
+ has_been_deleted = false;
+ is_empty = true;
+ feature_type_tag = 0;
+ f = 0;
+ f_null = true;
+ x = 0;
+ x_null = true;
+ x_offset = 0;
+ x_firmware_offset = 0;
+ x_homed = false;
+ y = 0;
+ y_null = true;
+ y_offset = 0;
+ y_firmware_offset = 0;
+ y_homed = false;
+ z = 0;
+ z_null = true;
+ z_offset = 0;
+ z_firmware_offset = 0;
+ z_homed = false;
+ is_relative = false;
+ is_relative_null = true;
+ is_extruder_relative = false;
+ is_extruder_relative_null = true;
+ is_metric = true;
+ is_metric_null = true;
+ last_extrusion_height = 0;
+ last_extrusion_height_null = true;
+ layer = 0;
+ height = 0;
+ height_increment = 0;
+ height_increment_change_count = 0;
+ is_printer_primed = false;
+ has_definite_position = false;
+ z_relative = 0;
+ is_in_position = false;
+ in_path_position = false;
+ is_zhop = false;
+ is_layer_change = false;
+ is_height_change = false;
+ is_height_increment_change = false;
+ is_xy_travel = false;
+ is_xyz_travel = false;
+ has_xy_position_changed = false;
+ has_position_changed = false;
+ has_received_home_command = false;
+ file_line_number = -1;
+ gcode_number = -1;
+ file_position = -1;
+ gcode_ignored = true;
+ is_in_bounds = true;
+ current_tool = 0;
+ p_extruders = NULL;
+ set_num_extruders(extruder_count);
+
+}
+
+position::position(const position &pos)
+{
+ has_been_deleted = false;
+ is_empty = pos.is_empty;
+ feature_type_tag = pos.feature_type_tag;
+ f = pos.f;
+ f_null = pos.f_null;
+ x = pos.x;
+ x_null = pos.x_null;
+ x_offset = pos.x_offset;
+ x_firmware_offset = pos.x_firmware_offset;
+ x_homed = pos.x_homed;
+ y = pos.y;
+ y_null = pos.y_null;
+ y_offset = pos.y_offset;
+ y_firmware_offset = pos.y_firmware_offset;
+ y_homed = pos.y_homed;
+ z = pos.z;
+ z_null = pos.z_null;
+ z_offset = pos.z_offset;
+ z_firmware_offset = pos.z_firmware_offset;
+ z_homed = pos.z_homed;
+ is_relative = pos.is_relative;
+ is_relative_null = pos.is_relative_null;
+ is_extruder_relative = pos.is_extruder_relative;
+ is_extruder_relative_null = pos.is_extruder_relative_null;
+ is_metric = pos.is_metric;
+ is_metric_null = pos.is_metric_null;
+ last_extrusion_height = pos.last_extrusion_height;
+ last_extrusion_height_null = pos.last_extrusion_height_null;
+ layer = pos.layer;
+ height = pos.height;
+ height_increment = pos.height_increment;
+ height_increment_change_count = pos.height_increment_change_count;
+ is_printer_primed = pos.is_printer_primed;
+ has_definite_position = pos.has_definite_position;
+ z_relative = pos.z_relative;
+ is_in_position = pos.is_in_position;
+ in_path_position = pos.in_path_position;
+ is_zhop = pos.is_zhop;
+ is_layer_change = pos.is_layer_change;
+ is_height_change = pos.is_height_change;
+ is_height_increment_change = pos.is_height_increment_change;
+ is_xy_travel = pos.is_xy_travel;
+ is_xyz_travel = pos.is_xyz_travel;
+ has_xy_position_changed = pos.has_xy_position_changed;
+ has_position_changed = pos.has_position_changed;
+ has_received_home_command = pos.has_received_home_command;
+ file_line_number = pos.file_line_number;
+ gcode_number = pos.gcode_number;
+ file_position = pos.file_position;
+ gcode_ignored = pos.gcode_ignored;
+ is_in_bounds = pos.is_in_bounds;
+ current_tool = pos.current_tool;
+ p_extruders = NULL;
+ command = pos.command;
+ num_extruders = 0;
+ set_num_extruders(pos.num_extruders);
+ for(int index=0; index < pos.num_extruders; index++)
+ {
+ p_extruders[index] = pos.p_extruders[index];
+ }
+}
+
+position::~position()
+{
+ if (has_been_deleted)
+ return;
+ has_been_deleted = true;
+ delete_extruders();
+}
+
+position& position::operator=(const position& pos) {
+ is_empty = pos.is_empty;
+ feature_type_tag = pos.feature_type_tag;
+ f = pos.f;
+ f_null = pos.f_null;
+ x = pos.x;
+ x_null = pos.x_null;
+ x_offset = pos.x_offset;
+ x_firmware_offset = pos.x_firmware_offset;
+ x_homed = pos.x_homed;
+ y = pos.y;
+ y_null = pos.y_null;
+ y_offset = pos.y_offset;
+ y_firmware_offset = pos.y_firmware_offset;
+ y_homed = pos.y_homed;
+ z = pos.z;
+ z_null = pos.z_null;
+ z_offset = pos.z_offset;
+ z_firmware_offset = pos.z_firmware_offset;
+ z_homed = pos.z_homed;
+ is_relative = pos.is_relative;
+ is_relative_null = pos.is_relative_null;
+ is_extruder_relative = pos.is_extruder_relative;
+ is_extruder_relative_null = pos.is_extruder_relative_null;
+ is_metric = pos.is_metric;
+ is_metric_null = pos.is_metric_null;
+ last_extrusion_height = pos.last_extrusion_height;
+ last_extrusion_height_null = pos.last_extrusion_height_null;
+ layer = pos.layer;
+ height = pos.height;
+ height_increment = pos.height_increment;
+ height_increment_change_count = pos.height_increment_change_count;
+ is_printer_primed = pos.is_printer_primed;
+ has_definite_position = pos.has_definite_position;
+ z_relative = pos.z_relative;
+ is_in_position = pos.is_in_position;
+ in_path_position = pos.in_path_position;
+ is_zhop = pos.is_zhop;
+ is_layer_change = pos.is_layer_change;
+ is_height_change = pos.is_height_change;
+ is_height_increment_change = pos.is_height_increment_change;
+ is_xy_travel = pos.is_xy_travel;
+ is_xyz_travel = pos.is_xyz_travel;
+ has_xy_position_changed = pos.has_xy_position_changed;
+ has_position_changed = pos.has_position_changed;
+ has_received_home_command = pos.has_received_home_command;
+ file_line_number = pos.file_line_number;
+ file_position = pos.file_position;
+ gcode_number = pos.gcode_number;
+ gcode_ignored = pos.gcode_ignored;
+ is_in_bounds = pos.is_in_bounds;
+ current_tool = pos.current_tool;
+ command = pos.command;
+ if (pos.num_extruders != num_extruders)
+ {
+ set_num_extruders(pos.num_extruders);
+ }
+
+
+ for (int index = 0; index < pos.num_extruders; index++)
+ {
+ p_extruders[index] = pos.p_extruders[index];
+ }
+ return *this;
+}
+
+void position::set_num_extruders(int num_extruders_)
+{
+ if (num_extruders_ == num_extruders)
+ return;
+ delete_extruders();
+ num_extruders = num_extruders_;
+ if (num_extruders_ > 0)
+ {
+ p_extruders = new extruder[num_extruders_];
+ }
+ else
+ {
+ throw std::exception();
+ }
+}
+
+void position::delete_extruders()
+{
+ if (p_extruders != NULL)
+ {
+ delete[] p_extruders;
+ p_extruders = NULL;
+ }
+}
+
+double position::get_gcode_x() const
+{
+ return x - x_offset + x_firmware_offset;
+}
+
+double position::get_gcode_y() const
+{
+ return y - y_offset + y_firmware_offset;
+}
+
+double position::get_gcode_z() const
+{
+ return z - z_offset + z_firmware_offset;
+}
+
+extruder& position::get_current_extruder() const
+{
+ int tool_number = current_tool;
+ if (current_tool > num_extruders-1)
+ tool_number = num_extruders - 1;
+ else if (current_tool < 0)
+ tool_number = 0;
+ return p_extruders[tool_number];
+}
+
+extruder& position::get_extruder(int index) const
+{
+ if (index >= num_extruders)
+ index = num_extruders - 1;
+ else if (index < 0)
+ index = 0;
+ return p_extruders[index];
+}
+
+void position::reset_state()
+{
+ is_layer_change = false;
+ is_height_change = false;
+ is_height_increment_change = false;
+ is_xy_travel = false;
+ is_xyz_travel = false;
+ has_position_changed = false;
+ has_received_home_command = false;
+ gcode_ignored = true;
+
+ //is_in_bounds = true; // I dont' think we want to reset this every time since it's only calculated if the current position
+ // changes.
+ p_extruders[current_tool].e_relative = 0;
+ z_relative = 0;
+ feature_type_tag = 0;
+}
diff --git a/GcodeProcessorLib/position.h b/GcodeProcessorLib/position.h
new file mode 100644
index 0000000..0958cd5
--- /dev/null
+++ b/GcodeProcessorLib/position.h
@@ -0,0 +1,106 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 POSITION_H
+#define POSITION_H
+#include <string>
+#include "parsed_command.h"
+#include "extruder.h"
+
+
+struct position
+{
+ position();
+ position(int extruder_count);
+ position(const position &pos); // Copy Constructor
+ virtual ~position();
+ position& operator=(const position& pos);
+ std::string to_string(bool rewrite, bool verbose, std::string additional_comment);
+ void reset_state();
+ parsed_command command;
+ int feature_type_tag;
+ double f;
+ bool f_null;
+ double x;
+ bool x_null;
+ double x_offset;
+ double x_firmware_offset;
+ bool x_homed;
+ double y;
+ bool y_null;
+ double y_offset;
+ double y_firmware_offset;
+ bool y_homed;
+ double z;
+ bool z_null;
+ double z_offset;
+ double z_firmware_offset;
+ bool z_homed;
+ bool is_metric;
+ bool is_metric_null;
+ double last_extrusion_height;
+ bool last_extrusion_height_null;
+ long layer;
+ double height;
+ int height_increment;
+ int height_increment_change_count;
+ bool is_printer_primed;
+ bool has_definite_position;
+ double z_relative;
+ bool is_relative;
+ bool is_relative_null;
+ bool is_extruder_relative;
+ bool is_extruder_relative_null;
+ bool is_layer_change;
+ bool is_height_change;
+ bool is_height_increment_change;
+ bool is_xy_travel;
+ bool is_xyz_travel;
+ bool is_zhop;
+ bool has_position_changed;
+ bool has_xy_position_changed;
+ bool has_received_home_command;
+ bool is_in_position;
+ bool in_path_position;
+ long file_line_number;
+ long gcode_number;
+ long file_position;
+ bool gcode_ignored;
+ bool is_in_bounds;
+ bool is_empty;
+ int current_tool;
+ int num_extruders;
+ bool has_been_deleted;
+ extruder * p_extruders;
+ extruder& get_current_extruder() const;
+ extruder& get_extruder(int index) const;
+ void set_num_extruders(int num_extruders_);
+ void delete_extruders();
+ double get_gcode_x() const;
+ double get_gcode_y() const;
+ double get_gcode_z() const;
+ void set_xyz_axis_mode(const std::string& xyz_axis_default_mode);
+ void set_e_axis_mode(const std::string& e_axis_default_mode);
+ void set_units_default(const std::string& units_default);
+ bool can_take_snapshot();
+};
+#endif \ No newline at end of file
diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp
new file mode 100644
index 0000000..5a2c1d6
--- /dev/null
+++ b/GcodeProcessorLib/utilities.cpp
@@ -0,0 +1,173 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 "utilities.h"
+#include <math.h>
+#include <sstream>
+#include <iostream>
+
+// Had to increase the zero tolerance because prusa slicer doesn't always retract enough while wiping.
+const double ZERO_TOLERANCE = 0.000005;
+const std::string utilities::WHITESPACE_ = " \n\r\t\f\v";
+
+bool utilities::is_zero(double x)
+{
+ return abs(x) < ZERO_TOLERANCE;
+}
+
+int utilities::round_up_to_int(double x)
+{
+ return int(x + ZERO_TOLERANCE);
+}
+
+bool utilities::is_equal(double x, double y)
+{
+ double abs_difference = abs(x - y);
+ return abs_difference < ZERO_TOLERANCE;
+}
+
+bool utilities::greater_than(double x, double y)
+{
+ return x > y && !is_equal(x, y);
+}
+
+bool utilities::greater_than_or_equal(double x, double y)
+{
+ return x > y || is_equal(x, y);
+}
+
+bool utilities::less_than(double x, double y)
+{
+ return x < y && !is_equal(x, y);
+}
+
+bool utilities::less_than_or_equal(double x, double y)
+{
+ return x < y || is_equal(x, y);
+}
+
+// custom tolerance functions
+bool utilities::is_zero(double x, double tolerance)
+{
+ return abs(x) < tolerance;
+}
+
+bool utilities::is_equal(double x, double y, double tolerance)
+{
+ double abs_difference = abs(x - y);
+ return abs_difference < tolerance;
+}
+
+int utilities::round_up_to_int(double x, double tolerance)
+{
+ return int(x + tolerance);
+}
+bool utilities::greater_than(double x, double y, double tolerance)
+{
+ return x > y && !utilities::is_equal(x, y, tolerance);
+}
+bool utilities::greater_than_or_equal(double x, double y, double tolerance)
+{
+ return x > y || utilities::is_equal(x, y, tolerance);
+}
+bool utilities::less_than(double x, double y, double tolerance)
+{
+ return x < y && !utilities::is_equal(x, y, tolerance);
+}
+bool utilities::less_than_or_equal(double x, double y, double tolerance)
+{
+ return x < y || utilities::is_equal(x, y, tolerance);
+}
+
+double utilities::get_cartesian_distance(double x1, double y1, double x2, double y2)
+{
+ // Compare the saved points cartesian distance from the current point
+ double xdif = x1 - x2;
+ double ydif = y1 - y2;
+ double dist_squared = xdif * xdif + ydif * ydif;
+ return sqrt(dist_squared);
+}
+
+double utilities::get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2)
+{
+ // Compare the saved points cartesian distance from the current point
+ double xdif = x1 - x2;
+ double ydif = y1 - y2;
+ double zdif = z1 - z2;
+ double dist_squared = xdif * xdif + ydif * ydif + zdif * zdif;
+ return sqrt(dist_squared);
+}
+
+std::string utilities::to_string(double value)
+{
+ std::ostringstream os;
+ os << value;
+ return os.str();
+}
+
+std::string utilities::ltrim(const std::string& s)
+{
+ size_t start = s.find_first_not_of(WHITESPACE_);
+ return (start == std::string::npos) ? "" : s.substr(start);
+}
+
+std::string utilities::rtrim(const std::string& s)
+{
+ size_t end = s.find_last_not_of(WHITESPACE_);
+ return (end == std::string::npos) ? "" : s.substr(0, end + 1);
+}
+
+std::string utilities::trim(const std::string& s)
+{
+ return rtrim(ltrim(s));
+}
+
+std::istream& utilities::safe_get_line(std::istream& is, std::string& t)
+{
+ t.clear();
+ // The characters in the stream are read one-by-one using a std::streambuf.
+ // That is faster than reading them one-by-one using the std::istream.
+ // Code that uses streambuf this way must be guarded by a sentry object.
+ // The sentry object performs various tasks,
+ // such as thread synchronization and updating the stream state.
+
+ std::istream::sentry se(is, true);
+ std::streambuf* sb = is.rdbuf();
+
+ for (;;) {
+ const int c = sb->sbumpc();
+ switch (c) {
+ case '\n':
+ return is;
+ case '\r':
+ if (sb->sgetc() == '\n')
+ sb->sbumpc();
+ return is;
+ case EOF:
+ // Also handle the case when the last line has no line ending
+ if (t.empty())
+ is.setstate(std::ios::eofbit);
+ return is;
+ default:
+ t += static_cast<char>(c);
+ }
+ }
+}
diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h
new file mode 100644
index 0000000..2a79d80
--- /dev/null
+++ b/GcodeProcessorLib/utilities.h
@@ -0,0 +1,57 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 <string>
+
+class utilities{
+public:
+ static bool is_zero(double x);
+ static int round_up_to_int(double x);
+ static bool is_equal(double x, double y);
+ static bool greater_than(double x, double y);
+ static bool greater_than_or_equal(double x, double y);
+ static bool less_than(double x, double y);
+ static bool less_than_or_equal(double x, double y);
+
+ // custom tolerance functions
+ static bool is_zero(double x, double tolerance);
+ static bool is_equal(double x, double y, double tolerance);
+ static int round_up_to_int(double x, double tolerance);
+ static bool greater_than(double x, double y, double tolerance);
+ static bool greater_than_or_equal(double x, double y, double tolerance);
+ static bool less_than(double x, double y, double tolerance);
+ static bool less_than_or_equal(double x, double y, double tolerance);
+
+ static double get_cartesian_distance(double x1, double y1, double x2, double y2);
+ static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2);
+ static std::string to_string(double value);
+ static std::string ltrim(const std::string& s);
+ static std::string rtrim(const std::string& s);
+ static std::string trim(const std::string& s);
+ static std::istream& safe_get_line(std::istream& is, std::string& t);
+protected:
+ static const std::string WHITESPACE_;
+private:
+ utilities();
+
+};
diff --git a/PyArcWelder/PyArcWelder.vcxproj b/PyArcWelder/PyArcWelder.vcxproj
new file mode 100644
index 0000000..a17c4c6
--- /dev/null
+++ b/PyArcWelder/PyArcWelder.vcxproj
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{DB476DBA-77D5-40A7-ADAB-D9901F32B270}</ProjectGuid>
+ <RootNamespace>PyArcWelder</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>C:\Python27\include;$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>C:\Python27\include;$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>C:\Python27\include;$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>C:\Python27\include;$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>C:\Python27\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>C:\Python27\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>C:\Python27\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>C:\Python27\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="python_helpers.h" />
+ <ClInclude Include="py_arc_welder.h" />
+ <ClInclude Include="py_arc_welder_extension.h" />
+ <ClInclude Include="py_logger.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="python_helpers.cpp" />
+ <ClCompile Include="py_arc_welder.cpp" />
+ <ClCompile Include="py_arc_welder_extension.cpp" />
+ <ClCompile Include="py_logger.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\ArcWelder\ArcWelder.vcxproj">
+ <Project>{1a4dbab1-bb42-4db1-b168-f113784efcef}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\GcodeProcessorLib\GcodeProcessorLib.vcxproj">
+ <Project>{31478bae-104b-4cc3-9876-42fa90cbd5fe}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/PyArcWelder/PyArcWelder.vcxproj.filters b/PyArcWelder/PyArcWelder.vcxproj.filters
new file mode 100644
index 0000000..8544f81
--- /dev/null
+++ b/PyArcWelder/PyArcWelder.vcxproj.filters
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="py_arc_welder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="py_arc_welder_extension.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="py_logger.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="python_helpers.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="py_arc_welder.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="py_arc_welder_extension.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="py_logger.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="python_helpers.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp
new file mode 100644
index 0000000..5768b40
--- /dev/null
+++ b/PyArcWelder/py_arc_welder.cpp
@@ -0,0 +1,48 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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 "py_arc_welder.h"
+
+
+bool py_arc_welder::on_progress_(double percent_complete, double seconds_elapsed, double estimated_seconds_remaining, int gcodes_processed, int current_line, int points_compressed, int arcs_created)
+{
+ PyObject* funcArgs = Py_BuildValue("(d,d,d,i,i,i,i)", percent_complete, seconds_elapsed, estimated_seconds_remaining, gcodes_processed, current_line, points_compressed, arcs_created);
+ if (funcArgs == NULL)
+ {
+ return false;
+ }
+
+ PyGILState_STATE gstate = PyGILState_Ensure();
+ PyObject* pContinueProcessing = PyObject_CallObject(py_progress_callback_, funcArgs);
+ Py_DECREF(funcArgs);
+ bool continue_processing = PyLong_AsLong(pContinueProcessing) > 0;
+ Py_DECREF(pContinueProcessing);
+ PyGILState_Release(gstate);
+
+ if (pContinueProcessing == NULL || pContinueProcessing == Py_None)
+ {
+ // no return value was supply, assume true
+
+ return true;
+ }
+ return continue_processing;
+}
diff --git a/PyArcWelder/py_arc_welder.h b/PyArcWelder/py_arc_welder.h
new file mode 100644
index 0000000..64bfd71
--- /dev/null
+++ b/PyArcWelder/py_arc_welder.h
@@ -0,0 +1,49 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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 <arc_welder.h>
+#include <string>
+#include "py_logger.h"
+#ifdef _DEBUG
+#undef _DEBUG
+#include <Python.h>
+#define _DEBUG
+#else
+#include <Python.h>
+#endif
+class py_arc_welder : public arc_welder
+{
+public:
+ py_arc_welder(std::string source_path, std::string target_path, py_logger* logger, double resolution_mm, bool g90_g91_influences_extruder, int buffer_size, PyObject* py_progress_callback):arc_welder(source_path, target_path, logger, resolution_mm, g90_g91_influences_extruder, buffer_size)
+ {
+ py_progress_callback_ = py_progress_callback;
+ }
+ virtual ~py_arc_welder() {
+
+ }
+protected:
+ virtual bool on_progress_(double percent_complete, double seconds_elapsed, double estimated_seconds_remaining, int gcodes_processed, int current_line, int points_compressed, int arcs_created);
+private:
+ PyObject* py_progress_callback_;
+};
+
diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp
new file mode 100644
index 0000000..eaf9923
--- /dev/null
+++ b/PyArcWelder/py_arc_welder_extension.cpp
@@ -0,0 +1,281 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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 "py_arc_welder_extension.h"
+#include "py_arc_welder.h"
+#include <iomanip>
+#include <sstream>
+#include <iostream>
+#include "arc_welder.h"
+#include "py_logger.h"
+#include "python_helpers.h"
+
+#if PY_MAJOR_VERSION >= 3
+int main(int argc, char* argv[])
+{
+ wchar_t* program = Py_DecodeLocale(argv[0], NULL);
+ if (program == NULL) {
+ fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
+ exit(1);
+ }
+
+ // Add a built-in module, before Py_Initialize
+ PyImport_AppendInittab("PyArcWelder", PyInit_PyGcodeArcConverter);
+
+ // Pass argv[0] to the Python interpreter
+ Py_SetProgramName(program);
+
+ // Initialize the Python interpreter. Required.
+ Py_Initialize();
+ std::cout << "Initializing threads...";
+ if (!PyEval_ThreadsInitialized()) {
+ PyEval_InitThreads();
+ }
+ // Optionally import the module; alternatively, import can be deferred until the embedded script imports it.
+ PyImport_ImportModule("PyArcWelder");
+ PyMem_RawFree(program);
+ return 0;
+}
+
+#else
+
+int main(int argc, char* argv[])
+{
+ Py_SetProgramName(argv[0]);
+ Py_Initialize();
+ if (!PyEval_ThreadsInitialized()) {
+ PyEval_InitThreads();
+ }
+ initPyGcodeArcConverter();
+ return 0;
+
+}
+#endif
+
+struct module_state {
+ PyObject* error;
+};
+#if PY_MAJOR_VERSION >= 3
+#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
+#else
+#define GETSTATE(m) (&_state)
+static struct module_state _state;
+#endif
+
+// Python 2 module method definition
+static PyMethodDef PyGcodeArcConverterMethods[] = {
+ { "ConvertFile", (PyCFunction)ConvertFile, METH_VARARGS ,"Converts segmented curve approximations to actual G2/G3 arcs within the supplied resolution." },
+ { NULL, NULL, 0, NULL }
+};
+
+// Python 3 module method definition
+#if PY_MAJOR_VERSION >= 3
+static int PyGcodeArcConverter_traverse(PyObject* m, visitproc visit, void* arg) {
+ Py_VISIT(GETSTATE(m)->error);
+ return 0;
+}
+
+static int PyGcodeArcConverter_clear(PyObject* m) {
+ Py_CLEAR(GETSTATE(m)->error);
+ return 0;
+}
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "PyGcodeArcConverter",
+ NULL,
+ sizeof(struct module_state),
+ PyGcodeArcConverterMethods,
+ NULL,
+ PyGcodeArcConverter_traverse,
+ PyGcodeArcConverter_clear,
+ NULL
+};
+
+#define INITERROR return NULL
+
+PyMODINIT_FUNC
+PyInit_PyGcodeArcConverter(void)
+
+#else
+#define INITERROR return
+
+extern "C" void initPyGcodeArcConverter(void)
+#endif
+{
+ std::cout << "Initializing PyGcodeArcConverter V0.1.0rc1.dev0 - Copyright (C) 2019 Brad Hochgesang...";
+
+#if PY_MAJOR_VERSION >= 3
+ std::cout << "Python 3+ Detected...";
+ PyObject* module = PyModule_Create(&moduledef);
+#else
+ std::cout << "Python 2 Detected...";
+ PyObject* module = Py_InitModule("PyGcodeArcConverter", PyGcodeArcConverterMethods);
+#endif
+
+ if (module == NULL)
+ INITERROR;
+ struct module_state* st = GETSTATE(module);
+
+ st->error = PyErr_NewException((char*)"PyGcodeArcConverter.Error", NULL, NULL);
+ if (st->error == NULL) {
+ Py_DECREF(module);
+ INITERROR;
+ }
+ std::vector<std::string> logger_names;
+ logger_names.push_back("arc_welder.gcode_conversion");
+ std::vector<int> logger_levels;
+ logger_levels.push_back(log_levels::DEBUG);
+ p_py_logger = new py_logger(logger_names, logger_levels);
+ p_py_logger->initialize_loggers();
+ std::string message = "PyArcWelder V0.1.0rc1.dev0 imported - Copyright (C) 2019 Brad Hochgesang...";
+ p_py_logger->log(GCODE_CONVERSION, INFO, message);
+ p_py_logger->set_log_level_by_value(DEBUG);
+ std::cout << "complete\r\n";
+
+#if PY_MAJOR_VERSION >= 3
+ return module;
+#endif
+}
+
+extern "C"
+{
+ static PyObject* ConvertFile(PyObject* self, PyObject* py_args)
+ {
+ PyObject* py_convert_file_args;
+ if (!PyArg_ParseTuple(
+ py_args,
+ "O",
+ &py_convert_file_args
+ ))
+ {
+ std::string message = "py_gcode_arc_converter.ConvertFile - Cound not extract the parameters dictionary.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return NULL;
+ }
+
+ py_gcode_arc_args args;
+ PyObject* py_progress_callback = NULL;
+
+ if (!ParseArgs(py_convert_file_args, args, &py_progress_callback))
+ {
+ return NULL;
+ }
+ p_py_logger->set_log_level_by_value(args.log_level);
+ std::stringstream stream;
+ stream << "py_gcode_arc_converter.ConvertFile - Parameters received: source_file_path: '" <<
+ args.source_file_path << "', target_file_path:'" << args.target_file_path << "' resolution_mm:" <<
+ args.resolution_mm << ", g90_91_influences_extruder: " << (args.g90_g91_influences_extruder ? "True" : "False") << "\n";
+ p_py_logger->log(GCODE_CONVERSION, INFO, stream.str());
+
+ std::string message = "py_gcode_arc_converter.ConvertFile - Beginning Arc Conversion.";
+ p_py_logger->log(GCODE_CONVERSION, INFO, message);
+
+ py_arc_welder arc_welder_obj(args.source_file_path, args.target_file_path, p_py_logger, args.resolution_mm, args.g90_g91_influences_extruder, 50, py_progress_callback);
+ arc_welder_obj.process();
+ message = "py_gcode_arc_converter.ConvertFile - Arc Conversion Complete.";
+ p_py_logger->log(GCODE_CONVERSION, INFO, message);
+ Py_XDECREF(py_progress_callback);
+ // For now just return py_none
+ return PyTuple_Pack(1, Py_None);
+ }
+}
+
+static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** py_progress_callback)
+{
+ p_py_logger->log(
+ GCODE_CONVERSION, INFO,
+ "Parsing GCode Conversion Args."
+ );
+
+ // Extract the source file path
+ PyObject* py_source_file_path = PyDict_GetItemString(py_args, "source_file_path");
+ if (py_source_file_path == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the source_file_path parameter from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.source_file_path = gcode_arc_converter::PyUnicode_SafeAsString(py_source_file_path);
+
+ // Extract the target file path
+ PyObject* py_target_file_path = PyDict_GetItemString(py_args, "target_file_path");
+ if (py_target_file_path == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the target_file_path parameter from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.target_file_path = gcode_arc_converter::PyUnicode_SafeAsString(py_target_file_path);
+
+ // Extract the resolution in millimeters
+ PyObject* py_resolution_mm = PyDict_GetItemString(py_args, "resolution_mm");
+ if (py_resolution_mm == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the resolution_mm parameter from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.resolution_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_resolution_mm);
+ if (args.resolution_mm <= 0)
+ {
+ args.resolution_mm = 0.05; // Set to the default if no resolution is provided, or if it is less than 0.
+ }
+ // Extract G90/G91 influences extruder
+ // g90_influences_extruder
+ PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder");
+ if (py_g90_g91_influences_extruder == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve g90_g91_influences_extruder from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.g90_g91_influences_extruder = PyLong_AsLong(py_g90_g91_influences_extruder) > 0;
+
+ // on_progress_received
+ PyObject* py_on_progress_received = PyDict_GetItemString(py_args, "on_progress_received");
+ if (py_on_progress_received == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve on_progress_received from the stabilization args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ // need to incref this so it doesn't vanish later (borrowed reference we are saving)
+ Py_XINCREF(py_on_progress_received);
+ *py_progress_callback = py_on_progress_received;
+
+ // Extract log_level
+ PyObject* py_log_level = PyDict_GetItemString(py_args, "log_level");
+ if (py_log_level == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve log_level from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+
+ int log_level_value = static_cast<int>(PyLong_AsLong(py_log_level));
+ // determine the log level as an index rather than as a value
+ args.log_level = p_py_logger->get_log_level_for_value(log_level_value);
+
+ return true;
+}
+
diff --git a/PyArcWelder/py_arc_welder_extension.h b/PyArcWelder/py_arc_welder_extension.h
new file mode 100644
index 0000000..7a3786d
--- /dev/null
+++ b/PyArcWelder/py_arc_welder_extension.h
@@ -0,0 +1,78 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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
+#ifdef _DEBUG
+#undef _DEBUG
+#include <Python.h>
+#define _DEBUG
+#else
+#include <Python.h>
+#endif
+#include <string>
+#include "py_logger.h"
+extern "C"
+{
+#if PY_MAJOR_VERSION >= 3
+ PyMODINIT_FUNC PyInit_PyGcodeArcConverter(void);
+#else
+ extern "C" void initPyGcodeArcConverter(void);
+#endif
+ static PyObject* ConvertFile(PyObject* self, PyObject* args);
+}
+
+struct py_gcode_arc_args {
+ py_gcode_arc_args() {
+ source_file_path = "";
+ target_file_path = "";
+ resolution_mm = 0.05;
+ g90_g91_influences_extruder = false;
+ log_level = 0;
+ }
+ py_gcode_arc_args(std::string source_file_path_, std::string target_file_path_, double resolution_mm_, bool g90_g91_influences_extruder_, int log_level_) {
+ source_file_path = source_file_path_;
+ target_file_path = target_file_path_;
+ resolution_mm = resolution_mm_;
+ g90_g91_influences_extruder = g90_g91_influences_extruder_;
+ log_level = log_level_;
+ }
+ std::string source_file_path;
+ std::string target_file_path;
+ double resolution_mm;
+ bool g90_g91_influences_extruder;
+ int log_level;
+};
+
+static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** p_py_progress_callback);
+
+// global logger
+py_logger* p_py_logger = NULL;
+/*
+static void AtExit()
+{
+ if (p_py_logger != NULL) delete p_py_logger;
+}*/
+
+
+
+
+
diff --git a/PyArcWelder/py_logger.cpp b/PyArcWelder/py_logger.cpp
new file mode 100644
index 0000000..d05e059
--- /dev/null
+++ b/PyArcWelder/py_logger.cpp
@@ -0,0 +1,246 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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 "py_logger.h"
+
+py_logger::py_logger(std::vector<std::string> names, std::vector<int> levels) : logger(names, levels)
+{
+ loggers_created_ = false;
+ check_log_levels_real_time = true;
+ py_logging_module = NULL;
+ py_logging_configurator_name = NULL;
+ py_logging_configurator = NULL;
+ py_arc_welder_gcode_conversion_logger = NULL;
+ gcode_conversion_log_level = 0;
+ py_info_function_name = NULL;
+ py_warn_function_name = NULL;
+ py_error_function_name = NULL;
+ py_debug_function_name = NULL;
+ py_verbose_function_name = NULL;
+ py_critical_function_name = NULL;
+ py_get_effective_level_function_name = NULL;
+}
+void py_logger::initialize_loggers()
+{
+ // Create all of the objects necessary for logging
+ // Import the arc_welder.log module
+ py_logging_module = PyImport_ImportModuleNoBlock("octoprint_arc_welder.log");
+ if (py_logging_module == NULL)
+ {
+ PyErr_SetString(PyExc_ImportError, "Could not import module 'arc_welder.log'.");
+ return;
+ }
+
+ // Get the logging configurator attribute string
+ py_logging_configurator_name = PyObject_GetAttrString(py_logging_module, "LoggingConfigurator");
+ if (py_logging_configurator_name == NULL)
+ {
+ PyErr_SetString(PyExc_ImportError, "Could not acquire the LoggingConfigurator attribute string.");
+ return;
+ }
+
+ // Create a logging configurator
+ PyGILState_STATE gstate = PyGILState_Ensure();
+ std::cout << "Creating arguments for LoggingConfigurator creation.\r\n";
+ PyObject* funcArgs = Py_BuildValue("(s,s,s)", "arc_welder", "arc_welder.", "octoprint_arc_welder.");
+ if (funcArgs == NULL)
+ {
+ std::cout << "Unable to create LoggingConfigurator arguments, exiting.\r\n";
+ PyErr_SetString(PyExc_ImportError, "Could not create LoggingConfigurator arguments.");
+ return;
+ }
+ std::cout << "Creating LoggingConfigurator...";
+ py_logging_configurator = PyObject_CallObject(py_logging_configurator_name, funcArgs);
+ std::cout << "Complete.\r\n";
+ Py_DECREF(funcArgs);
+ PyGILState_Release(gstate);
+ if (py_logging_configurator == NULL)
+ {
+ std::cout << "The LoggingConfigurator is null, exiting.\r\n";
+ PyErr_SetString(PyExc_ImportError, "Could not create a new instance of LoggingConfigurator.");
+ return;
+ }
+
+ // Create the gcode_parser logging object
+ py_arc_welder_gcode_conversion_logger = PyObject_CallMethod(py_logging_configurator, (char*)"get_logger", (char*)"s", "octoprint_arc_welder.gcode_conversion");
+ if (py_arc_welder_gcode_conversion_logger == NULL)
+ {
+ std::cout << "No child logger was created, exiting.\r\n";
+ PyErr_SetString(PyExc_ImportError, "Could not create the arc_welder.gcode_parser child logger.");
+ return;
+ }
+
+ // create the function name py objects
+ py_info_function_name = gcode_arc_converter::PyString_SafeFromString("info");
+ py_warn_function_name = gcode_arc_converter::PyString_SafeFromString("warn");
+ py_error_function_name = gcode_arc_converter::PyString_SafeFromString("error");
+ py_debug_function_name = gcode_arc_converter::PyString_SafeFromString("debug");
+ py_verbose_function_name = gcode_arc_converter::PyString_SafeFromString("verbose");
+ py_critical_function_name = gcode_arc_converter::PyString_SafeFromString("critical");
+ py_get_effective_level_function_name = gcode_arc_converter::PyString_SafeFromString("getEffectiveLevel");
+ loggers_created_ = true;
+ std::cout << "Logger created successfully.\r\n";
+
+}
+
+void py_logger::set_internal_log_levels(bool check_real_time)
+{
+ check_log_levels_real_time = check_real_time;
+ if (!check_log_levels_real_time)
+ {
+
+ PyObject* py_gcode_conversion_log_level = PyObject_CallMethodObjArgs(py_arc_welder_gcode_conversion_logger, py_get_effective_level_function_name, NULL);
+ if (py_gcode_conversion_log_level == NULL)
+ {
+ PyErr_Print();
+ PyErr_SetString(PyExc_ValueError, "Logging.arc_welder - Could not retrieve the log level for the gcode parser logger.");
+ }
+ gcode_conversion_log_level = gcode_arc_converter::PyIntOrLong_AsLong(py_gcode_conversion_log_level);
+
+ Py_XDECREF(py_gcode_conversion_log_level);
+ }
+}
+
+void py_logger::log_exception(const int logger_type, const std::string& message)
+{
+ log(logger_type, ERROR, message, true);
+}
+
+void py_logger::log(const int logger_type, const int log_level, const std::string& message)
+{
+ log(logger_type, log_level, message, false);
+}
+
+void py_logger::log(const int logger_type, const int log_level, const std::string& message, bool is_exception)
+{
+ if (!loggers_created_)
+ return;
+
+ // Get the appropriate logger
+ PyObject* py_logger;
+ long current_log_level = 0;
+ switch (logger_type)
+ {
+ case GCODE_CONVERSION:
+ py_logger = py_arc_welder_gcode_conversion_logger;
+ current_log_level = gcode_conversion_log_level;
+ break;
+ default:
+ std::cout << "Logging.arc_welder_log - unknown logger_type.\r\n";
+ PyErr_SetString(PyExc_ValueError, "Logging.arc_welder_log - unknown logger_type.");
+ return;
+ }
+
+ if (!check_log_levels_real_time)
+ {
+ //std::cout << "Current Log Level: " << current_log_level << " requested:" << log_level;
+ // For speed we are going to check the log levels here before attempting to send any logging info to Python.
+ if (current_log_level > log_level)
+ {
+ return;
+ }
+ }
+
+ PyObject* pyFunctionName = NULL;
+
+ PyObject* error_type = NULL;
+ PyObject* error_value = NULL;
+ PyObject* error_traceback = NULL;
+ bool error_occurred = false;
+ if (is_exception)
+ {
+ // if an error has occurred, use the exception function to log the entire error
+ pyFunctionName = py_error_function_name;
+ if (PyErr_Occurred())
+ {
+ error_occurred = true;
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+ PyErr_NormalizeException(&error_type, &error_value, &error_traceback);
+ }
+ }
+ else
+ {
+ switch (log_level)
+ {
+ case INFO:
+ pyFunctionName = py_info_function_name;
+ break;
+ case WARNING:
+ pyFunctionName = py_warn_function_name;
+ break;
+ case ERROR:
+ pyFunctionName = py_error_function_name;
+ break;
+ case DEBUG:
+ pyFunctionName = py_debug_function_name;
+ break;
+ case VERBOSE:
+ pyFunctionName = py_verbose_function_name;
+ break;
+ case CRITICAL:
+ pyFunctionName = py_critical_function_name;
+ break;
+ }
+ }
+ PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(message);
+ if (pyMessage == NULL)
+ {
+ std::cout << "Unable to convert the log message '" << message.c_str() << "' to a PyString/Unicode message.\r\n";
+ PyErr_Format(PyExc_ValueError,
+ "Unable to convert the log message '%s' to a PyString/Unicode message.", message.c_str());
+ return;
+ }
+ PyGILState_STATE state = PyGILState_Ensure();
+ PyObject* ret_val = PyObject_CallMethodObjArgs(py_logger, pyFunctionName, pyMessage, NULL);
+ // We need to decref our message so that the GC can remove it. Maybe?
+ Py_DECREF(pyMessage);
+ PyGILState_Release(state);
+ if (ret_val == NULL)
+ {
+ if (!PyErr_Occurred())
+ {
+ std::cout << "Logging.arc_welder_log - null was returned from the specified logger.\r\n";
+ PyErr_SetString(PyExc_ValueError, "Logging.arc_welder_log - null was returned from the specified logger.");
+ }
+ else
+ {
+ std::cout << "Logging.arc_welder_log - null was returned from the specified logger and an error was detected.\r\n";
+ // I'm not sure what else to do here since I can't log the error. I will print it
+ // so that it shows up in the console, but I can't log it, and there is no way to
+ // return an error.
+ PyErr_Print();
+ PyErr_Clear();
+ }
+ }
+ else
+ {
+ // Set the exception if we are doing exception logging.
+ if (is_exception)
+ {
+ if (error_occurred)
+ PyErr_Restore(error_type, error_value, error_traceback);
+ else
+ PyErr_SetString(PyExc_Exception, message.c_str());
+ }
+ }
+ Py_XDECREF(ret_val);
+}
diff --git a/PyArcWelder/py_logger.h b/PyArcWelder/py_logger.h
new file mode 100644
index 0000000..15fa5fa
--- /dev/null
+++ b/PyArcWelder/py_logger.h
@@ -0,0 +1,66 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include <string>
+#include <vector>
+#include <map>
+#include "logger.h"
+#ifdef _DEBUG
+#undef _DEBUG
+#include <Python.h>
+#define _DEBUG
+#else
+#include <Python.h>
+#endif
+#include <string>
+#include "python_helpers.h"
+#include <iostream>
+enum py_loggers { GCODE_CONVERSION };
+
+class py_logger : public logger {
+public:
+ py_logger(std::vector<std::string> names, std::vector<int> levels);
+ virtual ~py_logger() {
+ }
+ void initialize_loggers();
+ void set_internal_log_levels(bool check_real_time);
+ virtual void log(const int logger_type, const int log_level, const std::string& message);
+ virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception);
+ virtual void log_exception(const int logger_type, const std::string& message);
+private:
+ bool check_log_levels_real_time;
+ PyObject* py_logging_module;
+ PyObject* py_logging_configurator_name;
+ PyObject* py_logging_configurator;
+ PyObject* py_arc_welder_gcode_conversion_logger;
+ long gcode_conversion_log_level;
+ PyObject* py_info_function_name;
+ PyObject* py_warn_function_name;
+ PyObject* py_error_function_name;
+ PyObject* py_debug_function_name;
+ PyObject* py_verbose_function_name;
+ PyObject* py_critical_function_name;
+ PyObject* py_get_effective_level_function_name;
+};
+
diff --git a/PyArcWelder/python_helpers.cpp b/PyArcWelder/python_helpers.cpp
new file mode 100644
index 0000000..acaae19
--- /dev/null
+++ b/PyArcWelder/python_helpers.cpp
@@ -0,0 +1,116 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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 "python_helpers.h"
+namespace gcode_arc_converter {
+
+ int PyUnicode_SafeCheck(PyObject* py)
+ {
+#if PY_MAJOR_VERSION >= 3
+ return PyUnicode_Check(py);
+#else
+ return PyUnicode_Check(py);
+#endif
+ }
+
+ const char* PyUnicode_SafeAsString(PyObject* py)
+ {
+#if PY_MAJOR_VERSION >= 3
+ return PyUnicode_AsUTF8(py);
+#else
+ return (char*)PyString_AsString(py);
+#endif
+ }
+
+ PyObject* PyString_SafeFromString(const char* str)
+ {
+#if PY_MAJOR_VERSION >= 3
+ return PyUnicode_FromString(str);
+#else
+ return PyString_FromString(str);
+#endif
+ }
+
+ PyObject* PyUnicode_SafeFromString(std::string str)
+ {
+#if PY_MAJOR_VERSION >= 3
+ return PyUnicode_FromString(str.c_str());
+#else
+ // TODO: try PyUnicode_DecodeUnicodeEscape maybe?
+ //return PyUnicode_DecodeUTF8(str.c_str(), NULL, "replace");
+ PyObject* pyString = PyString_FromString(str.c_str());
+ if (pyString == NULL)
+ {
+ PyErr_Print();
+ std::string message = "Unable to convert the c_str to a python string: ";
+ message += str;
+ PyErr_SetString(PyExc_ValueError, message.c_str());
+ return NULL;
+ }
+ PyObject* pyUnicode = PyUnicode_FromEncodedObject(pyString, NULL, "replace");
+ Py_DECREF(pyString);
+ return pyUnicode;
+#endif
+ }
+
+ double PyFloatOrInt_AsDouble(PyObject* py_double_or_int)
+ {
+ if (PyFloat_CheckExact(py_double_or_int))
+ return PyFloat_AsDouble(py_double_or_int);
+#if PY_MAJOR_VERSION < 3
+ else if (PyInt_CheckExact(py_double_or_int))
+ return static_cast<double>(PyInt_AsLong(py_double_or_int));
+#endif
+ else if (PyLong_CheckExact(py_double_or_int))
+ return static_cast<double>(PyLong_AsLong(py_double_or_int));
+ return 0;
+ }
+
+ long PyIntOrLong_AsLong(PyObject* value)
+ {
+ long ret_val;
+#if PY_MAJOR_VERSION < 3
+ if (PyInt_Check(value))
+ {
+ ret_val = PyInt_AsLong(value);
+ }
+ else
+ {
+ ret_val = PyLong_AsLong(value);
+ }
+#else
+ ret_val = PyLong_AsLong(value);
+#endif
+ return ret_val;
+ }
+
+ bool PyFloatLongOrInt_Check(PyObject* py_object)
+ {
+ return (
+ PyFloat_Check(py_object) || PyLong_Check(py_object)
+#if PY_MAJOR_VERSION < 3
+ || PyInt_Check(py_object)
+#endif
+ );
+
+ }
+} \ No newline at end of file
diff --git a/PyArcWelder/python_helpers.h b/PyArcWelder/python_helpers.h
new file mode 100644
index 0000000..93ad23d
--- /dev/null
+++ b/PyArcWelder/python_helpers.h
@@ -0,0 +1,40 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin.
+//
+// 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.
+//
+// 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
+#ifdef _DEBUG
+#undef _DEBUG
+#include <Python.h>
+#define _DEBUG
+#else
+#include <Python.h>
+#endif
+#include <string>
+namespace gcode_arc_converter {
+ int PyUnicode_SafeCheck(PyObject* py);
+ const char* PyUnicode_SafeAsString(PyObject* py);
+ PyObject* PyString_SafeFromString(const char* str);
+ PyObject* PyUnicode_SafeFromString(std::string str);
+ double PyFloatOrInt_AsDouble(PyObject* py_double_or_int);
+ long PyIntOrLong_AsLong(PyObject* value);
+ bool PyFloatLongOrInt_Check(PyObject* value);
+} \ No newline at end of file