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