From 9f7722d369eefcfe610f216ea8da2107bc17863a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Wed, 12 Oct 2022 01:16:34 +0100 Subject: [PATCH] v3.7.0 - **File formats:** - (Add) `TransitionLayerCount` modifier to: Chitubox Zip, CWS, JXS, OSLA, PW*, UVJ, ZCodex, ZCode - (Add) Utility methods for transition layers calculation/parse - (Improvement) Calculate and set `TransitionLayerCount` property in file decode based on layer exposure time configuration - **GCode:** - (Improvement) GCode: Able to parse layer image file with appended numbers on the filename (Afecting CWS) (#577) - (Fix) Bad parsing of the file when it comes from Lychee or NovaMaker slicer (Afecting CWS) - (Fix) Incorrect parse of "Wait time before cure" from layers when printer require wait sync moves (Afecting CWS) - **Tools:** - (Add) External tests: The Complete Resin 3D Printing Settings Guide for Beginners - (Add) External tests: 9 settings for faster printing - (Improvement) Fade exposure time: Set `TransitionLayerCount` property with the affected layer count - **Suggestions:** - (Add) Transition layers: If you are printing flat on the build plate your model will print better when using a smooth transition exposure time instead of a harsh variation, resulting in reduced layer line effect and avoid possible problems due the large exposure difference. This is not so important when your model print raised under a raft/supports unaffected by the bottom exposure, in that case, it's fine to ignore this. - (Add) Model position: Printing on a corner will reduce the FEP stretch forces when detaching from the model during a lift sequence, benefits are: Reduced lift height and faster printing, less stretch, less FEP marks, better FEP lifespan, easier to peel, less prone to failure and use the screen pixels more evenly. If the model is too large to fit within the margin(s) on the screen, it will attempt to center it on that same axis to avoid touching on screen edge(s) and to give a sane margin from it. - **Status bar:** - (Add) Transition layers: 0/-0.00s - (Improvement) Change "Layer Height: 0.000mm" to "Layers: count @ 0.000mm" - (Improvement) Change "Bottom layers: 0" to "Bottom layers: 0/0.000mm" - (Change) Show user informative message about CTB Encrypted file format once per ten file loads - (Upgrade) .NET from 6.0.9 to 6.0.10 - (Fix) Windows MSI installation not upgrading well when downgrade libraries --- CHANGELOG.md | 27 ++ README.md | 1 - RELEASE_NOTES.md | 25 +- UVtools.Core/Extensions/NumberExtensions.cs | 252 +++++++++++++++ UVtools.Core/Extensions/StringExtensions.cs | 10 +- UVtools.Core/Extensions/TypeExtensions.cs | 2 - UVtools.Core/FileFormats/CWSFile.cs | 3 +- UVtools.Core/FileFormats/ChituboxFile.cs | 3 +- UVtools.Core/FileFormats/ChituboxZipFile.cs | 1 + UVtools.Core/FileFormats/FileFormat.cs | 168 ++++++++-- UVtools.Core/FileFormats/JXSFile.cs | 1 + UVtools.Core/FileFormats/OSLAFile.cs | 1 + .../FileFormats/PhotonWorkshopFile.cs | 3 +- UVtools.Core/FileFormats/SL1File.cs | 2 +- UVtools.Core/FileFormats/UVJFile.cs | 1 + UVtools.Core/FileFormats/ZCodeFile.cs | 1 + UVtools.Core/FileFormats/ZCodexFile.cs | 3 +- UVtools.Core/GCode/GCodeBuilder.cs | 41 ++- UVtools.Core/GCode/GCodeLayer.cs | 5 +- UVtools.Core/Managers/SuggestionManager.cs | 12 +- UVtools.Core/Network/MappedDevice.cs | 2 +- UVtools.Core/Network/RemotePrinterRequest.cs | 2 +- UVtools.Core/Objects/MappedProcess.cs | 2 +- .../Operations/OperationFadeExposureTime.cs | 13 +- UVtools.Core/Suggestions/Suggestion.cs | 13 +- .../Suggestions/SuggestionBottomLayerCount.cs | 30 +- .../Suggestions/SuggestionLayerHeight.cs | 7 +- .../Suggestions/SuggestionModelPosition.cs | 290 ++++++++++++++++++ .../SuggestionTransitionLayerCount.cs | 193 ++++++++++++ .../SuggestionWaitTimeAfterCure.cs | 5 +- .../SuggestionWaitTimeBeforeCure.cs | 5 +- UVtools.Core/UVtools.Core.csproj | 2 +- .../Code/HeatGeneratedFileList.wxs | 6 +- UVtools.Installer/Code/Product.wxs | 25 +- UVtools.Installer/UVtools.Installer.wixproj | 3 + .../CalibrateExternalTestsControl.axaml | 69 +++-- .../SuggestionModelPositionControl.axaml | 111 +++++++ .../SuggestionModelPositionControl.axaml.cs | 23 ++ ...uggestionTransitionLayerCountControl.axaml | 63 ++++ ...estionTransitionLayerCountControl.axaml.cs | 23 ++ .../Controls/Tools/ToolMorphControl.axaml | 58 ++-- UVtools.WPF/MainWindow.PixelEditor.cs | 1 + UVtools.WPF/MainWindow.Suggestions.cs | 62 +++- UVtools.WPF/MainWindow.axaml | 25 +- UVtools.WPF/MainWindow.axaml.cs | 10 +- UVtools.WPF/UVtools.WPF.csproj | 2 +- UVtools.WPF/UserSettings.cs | 9 + build/createRelease.ps1 | 2 +- documentation/UVtools.Core.xml | 128 +++++++- 49 files changed, 1580 insertions(+), 166 deletions(-) create mode 100644 UVtools.Core/Extensions/NumberExtensions.cs create mode 100644 UVtools.Core/Suggestions/SuggestionModelPosition.cs create mode 100644 UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs create mode 100644 UVtools.WPF/Controls/Suggestions/SuggestionModelPositionControl.axaml create mode 100644 UVtools.WPF/Controls/Suggestions/SuggestionModelPositionControl.axaml.cs create mode 100644 UVtools.WPF/Controls/Suggestions/SuggestionTransitionLayerCountControl.axaml create mode 100644 UVtools.WPF/Controls/Suggestions/SuggestionTransitionLayerCountControl.axaml.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1042e5a..2381245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## 12/10/2022 - v3.7.0 + +- **File formats:** + - (Add) `TransitionLayerCount` modifier to: Chitubox Zip, CWS, JXS, OSLA, PW*, UVJ, ZCodex, ZCode + - (Add) Utility methods for transition layers calculation/parse + - (Improvement) Calculate and set `TransitionLayerCount` property in file decode based on layer exposure time configuration +- **GCode:** + - (Improvement) GCode: Able to parse layer image file with appended numbers on the filename (Afecting CWS) (#577) + - (Fix) Bad parsing of the file when it comes from Lychee or NovaMaker slicer (Afecting CWS) + - (Fix) Incorrect parse of "Wait time before cure" from layers when printer require wait sync moves (Afecting CWS) +- **Tools:** + - (Add) External tests: The Complete Resin 3D Printing Settings Guide for Beginners + - (Add) External tests: 9 settings for faster printing + - (Improvement) Fade exposure time: Set `TransitionLayerCount` property with the affected layer count +- **Suggestions:** + - (Add) Transition layers: If you are printing flat on the build plate your model will print better when using a smooth transition exposure time instead of a harsh variation, resulting in reduced layer line effect and avoid possible problems due the large exposure difference. + This is not so important when your model print raised under a raft/supports unaffected by the bottom exposure, in that case, it's fine to ignore this. + - (Add) Model position: Printing on a corner will reduce the FEP stretch forces when detaching from the model during a lift sequence, benefits are: Reduced lift height and faster printing, less stretch, less FEP marks, better FEP lifespan, easier to peel, less prone to failure and use the screen pixels more evenly. + If the model is too large to fit within the margin(s) on the screen, it will attempt to center it on that same axis to avoid touching on screen edge(s) and to give a sane margin from it. +- **Status bar:** + - (Add) Transition layers: 0/-0.00s + - (Improvement) Change "Layer Height: 0.000mm" to "Layers: count @ 0.000mm" + - (Improvement) Change "Bottom layers: 0" to "Bottom layers: 0/0.000mm" +- (Change) Show user informative message about CTB Encrypted file format once per ten file loads +- (Upgrade) .NET from 6.0.9 to 6.0.10 +- (Fix) Windows MSI installation not upgrading well when downgrade libraries + ## 04/10/2022 - v3.6.8 - (Fix) Occasional crash when detecting islands and/or repair issues (#568, #569) diff --git a/README.md b/README.md index fa2b747..64b334c 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,6 @@ The following commands are the old way and commands under the UI executable, the **Copy the following script, paste and run on a terminal:** - ```bash [[ "$(command -v apt-get)" ]] && sudo apt-get install -y wget [[ "$(command -v pacman)" ]] && sudo pacman -S wget diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f07d248..3e8d757 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,2 +1,25 @@ -- (Fix) Occasional crash when detecting islands and/or repair issues (#568, #569) +- **File formats:** + - (Add) `TransitionLayerCount` modifier to: Chitubox Zip, CWS, JXS, OSLA, PW*, UVJ, ZCodex, ZCode + - (Add) Utility methods for transition layers calculation/parse + - (Improvement) Calculate and set `TransitionLayerCount` property in file decode based on layer exposure time configuration +- **GCode:** + - (Improvement) GCode: Able to parse layer image file with appended numbers on the filename (Afecting CWS) (#577) + - (Fix) Bad parsing of the file when it comes from Lychee or NovaMaker slicer (Afecting CWS) + - (Fix) Incorrect parse of "Wait time before cure" from layers when printer require wait sync moves (Afecting CWS) +- **Tools:** + - (Add) External tests: The Complete Resin 3D Printing Settings Guide for Beginners + - (Add) External tests: 9 settings for faster printing + - (Improvement) Fade exposure time: Set `TransitionLayerCount` property with the affected layer count +- **Suggestions:** + - (Add) Transition layers: If you are printing flat on the build plate your model will print better when using a smooth transition exposure time instead of a harsh variation, resulting in reduced layer line effect and avoid possible problems due the large exposure difference. + This is not so important when your model print raised under a raft/supports unaffected by the bottom exposure, in that case, it's fine to ignore this. + - (Add) Model position: Printing on a corner will reduce the FEP stretch forces when detaching from the model during a lift sequence, benefits are: Reduced lift height and faster printing, less stretch, less FEP marks, better FEP lifespan, easier to peel, less prone to failure and use the screen pixels more evenly. + If the model is too large to fit within the margin(s) on the screen, it will attempt to center it on that same axis to avoid touching on screen edge(s) and to give a sane margin from it. +- **Status bar:** + - (Add) Transition layers: 0/-0.00s + - (Improvement) Change "Layer Height: 0.000mm" to "Layers: count @ 0.000mm" + - (Improvement) Change "Bottom layers: 0" to "Bottom layers: 0/0.000mm" +- (Change) Show user informative message about CTB Encrypted file format once per ten file loads +- (Upgrade) .NET from 6.0.9 to 6.0.10 +- (Fix) Windows MSI installation not upgrading well when downgrade libraries diff --git a/UVtools.Core/Extensions/NumberExtensions.cs b/UVtools.Core/Extensions/NumberExtensions.cs new file mode 100644 index 0000000..dd13e86 --- /dev/null +++ b/UVtools.Core/Extensions/NumberExtensions.cs @@ -0,0 +1,252 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +namespace UVtools.Core.Extensions; + +public static class NumberExtensions +{ + /// + /// Convert bool to byte (0/1) + /// + /// + /// + public static byte ToByte(this bool value) => (byte)(value ? 1 : 0); + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this sbyte n) + { + if (n >= 0) + { + return n switch + { + < 10 => 1, + < 100 => 2, + _ => 3 + }; + } + + return n switch + { + > -10 => 1, + > -100 => 2, + _ => 3 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this byte n) + { + return n switch + { + < 10 => 1, + < 100 => 2, + _ => 3 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this short n) + { + if (n >= 0) + { + return n switch + { + < 10 => 1, + < 100 => 2, + < 1000 => 3, + _ => 4 + }; + } + + return n switch + { + > -10 => 1, + > -100 => 2, + > -1000 => 3, + _ => 4 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this ushort n) + { + return n switch + { + < 10 => 1, + < 100 => 2, + < 1000 => 3, + _ => 4 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this int n) + { + if (n >= 0) + { + return n switch + { + < 10 => 1, + < 100 => 2, + < 1000 => 3, + < 10000 => 4, + < 100000 => 5, + < 1000000 => 6, + < 10000000 => 7, + < 100000000 => 8, + < 1000000000 => 9, + _ => 10 + }; + } + + return n switch + { + > -10 => 1, + > -100 => 2, + > -1000 => 3, + > -10000 => 4, + > -100000 => 5, + > -1000000 => 6, + > -10000000 => 7, + > -100000000 => 8, + > -1000000000 => 9, + _ => 10 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this uint n) + { + return n switch + { + < 10U => 1, + < 100U => 2, + < 1000U => 3, + < 10000U => 4, + < 100000U => 5, + < 1000000U => 6, + < 10000000U => 7, + < 100000000U => 8, + < 1000000000U => 9, + _ => 10 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this long n) + { + if (n >= 0) + { + return n switch + { + < 10L => 1, + < 100L => 2, + < 1000L => 3, + < 10000L => 4, + < 100000L => 5, + < 1000000L => 6, + < 10000000L => 7, + < 100000000L => 8, + < 1000000000L => 9, + < 10000000000L => 10, + < 100000000000L => 11, + < 1000000000000L => 12, + < 10000000000000L => 13, + < 100000000000000L => 14, + < 1000000000000000L => 15, + < 10000000000000000L => 16, + < 100000000000000000L => 17, + < 1000000000000000000L => 18, + _ => 19 + }; + } + + return n switch + { + > -10L => 1, + > -100L => 2, + > -1000L => 3, + > -10000L => 4, + > -100000L => 5, + > -1000000L => 6, + > -10000000L => 7, + > -100000000L => 8, + > -1000000000L => 9, + > -10000000000L => 10, + > -100000000000L => 11, + > -1000000000000L => 12, + > -10000000000000L => 13, + > -100000000000000L => 14, + > -1000000000000000L => 15, + > -10000000000000000L => 16, + > -100000000000000000L => 17, + > -1000000000000000000L => 18, + _ => 19 + }; + } + + /// + /// Gets the number of digits this number haves + /// + /// + /// Digit count + public static int DigitCount(this ulong n) + { + return n switch + { + < 10UL => 1, + < 100UL => 2, + < 1000UL => 3, + < 10000UL => 4, + < 100000UL => 5, + < 1000000UL => 6, + < 10000000UL => 7, + < 100000000UL => 8, + < 1000000000UL => 9, + < 10000000000UL => 10, + < 100000000000UL => 11, + < 1000000000000UL => 12, + < 10000000000000UL => 13, + < 100000000000000UL => 14, + < 1000000000000000UL => 15, + < 10000000000000000UL => 16, + < 100000000000000000UL => 17, + < 1000000000000000000UL => 18, + < 10000000000000000000UL => 19, + _ => 20 + }; + } +} \ No newline at end of file diff --git a/UVtools.Core/Extensions/StringExtensions.cs b/UVtools.Core/Extensions/StringExtensions.cs index 23943d4..d30b2cb 100644 --- a/UVtools.Core/Extensions/StringExtensions.cs +++ b/UVtools.Core/Extensions/StringExtensions.cs @@ -34,8 +34,14 @@ public static class StringExtensions }; } - public static string Repeat(this string s, int n) - => n <= 0 ? string.Empty : new StringBuilder(s.Length * n).Insert(0, s, n).ToString(); + /// + /// Repeat this string times + /// + /// String to repeat + /// Number of times to repeat + /// repeated times + public static string Repeat(this string str, int count) + => count <= 0 ? string.Empty : new StringBuilder(str.Length * count).Insert(0, str, count).ToString(); /// /// Converts a string into a target type diff --git a/UVtools.Core/Extensions/TypeExtensions.cs b/UVtools.Core/Extensions/TypeExtensions.cs index 39be38e..1a8b331 100644 --- a/UVtools.Core/Extensions/TypeExtensions.cs +++ b/UVtools.Core/Extensions/TypeExtensions.cs @@ -39,8 +39,6 @@ public static class TypeExtensions return (T)instance; } - public static byte ToByte(this bool value) => (byte)(value ? 1 : 0); - public static IEnumerable GetTypesInNamespace(Assembly assembly, string nameSpace) { return assembly.GetTypes() diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs index 0414165..b4b5e0e 100644 --- a/UVtools.Core/FileFormats/CWSFile.cs +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -321,6 +321,7 @@ public class CWSFile : FileFormat public override PrintParameterModifier[]? PrintParameterModifiers { get; } = { PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.TransitionLayerCount, PrintParameterModifier.BottomWaitTimeBeforeCure, PrintParameterModifier.WaitTimeBeforeCure, @@ -840,7 +841,7 @@ public class CWSFile : FileFormat } } - DecodeLayersFromZipRegex(inputFile, @"(\d+).png", IndexStartNumber.Zero, progress); + DecodeLayersFromZipIgnoreFilename(inputFile, progress); GCode.ParseLayersFromGCode(this); } diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index 2b0b86c..eb7c6e9 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -1826,8 +1826,7 @@ public class ChituboxFile : FileFormat { if (HeaderSettings.EncryptionKey == 0) { - var rnd = new Random(); - HeaderSettings.EncryptionKey = (uint)rnd.Next(byte.MaxValue, int.MaxValue); + HeaderSettings.EncryptionKey = (uint)new Random().Next(byte.MaxValue, int.MaxValue); } } else diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs index 847f2ec..c6d0448 100644 --- a/UVtools.Core/FileFormats/ChituboxZipFile.cs +++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs @@ -85,6 +85,7 @@ public class ChituboxZipFile : FileFormat public override PrintParameterModifier[]? PrintParameterModifiers { get; } = { PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.TransitionLayerCount, PrintParameterModifier.BottomWaitTimeBeforeCure, PrintParameterModifier.WaitTimeBeforeCure, diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index f60faf6..a252117 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -1796,10 +1796,14 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable (byte)LayerCount.ToString().Length; + /// + /// Return the number of digits on the layer count number, eg: 123 layers = 3 digits + /// + public byte LayerDigits => (byte)LayerCount.DigitCount(); /// /// Gets or sets the total height for the bottom layers in millimeters @@ -1822,13 +1826,14 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable /// Gets the transition layer type /// - public virtual TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Firmware; + public virtual TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Software; /// /// Gets or sets the number of transition layers @@ -1840,6 +1845,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable CanUseLayerWaitTimeBeforeCure || CanUseLayerLightOffDelay; public bool CanUseLayerLightPWM => HaveLayerParameterModifier(PrintParameterModifier.LightPWM); + public string TransitionLayersRepresentation + { + get + { + var str = TransitionLayerCount.ToString(CultureInfo.InvariantCulture); + + if (!CanUseTransitionLayerCount) + { + return str; + } + + if (TransitionLayerCount > 0) + { + var decrement = ParseTransitionStepTimeFromLayers(); + if (decrement != 0) + { + str += $"/{-decrement}s"; + } + } + + return str; + } + } + public string ExposureRepresentation { get @@ -3420,6 +3452,15 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable Layer.HeightPrecision) + { + throw new FileLoadException($"The layer height ({LayerHeight}mm) have more decimal digits than the supported ({Layer.HeightPrecision}) digits.\n" + + "Lower and fix your layer height on slicer to avoid precision errors.", fileFullPath); + } + IsModified = false; StartingMaterialMilliliters = MaterialMilliliters; StartingMaterialCost = MaterialCost; @@ -3429,13 +3470,9 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable Layer.HeightPrecision) + if (CanUseTransitionLayerCount && TransitionLayerType == TransitionLayerTypes.Software) { - throw new FileLoadException($"The layer height ({LayerHeight}mm) have more decimal digits than the supported ({Layer.HeightPrecision}) digits.\n" + - "Lower and fix your layer height on slicer to avoid precision errors.", fileFullPath); + SuppressRebuildPropertiesWork(() => TransitionLayerCount = ParseTransitionLayerCountFromLayers()); } bool reSaveFile = Sanitize(); @@ -3632,14 +3669,11 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable 0) layerIndex--; - if (layerIndex >= LayerCount) - { - continue; - } + if (layerIndex >= LayerCount) continue; layerEntries[layerIndex] = entry; } @@ -3656,16 +3690,24 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable? matGenFunc = null) - => DecodeLayersFromZipRegex(zipArchive, $@"(\d{{{padDigits}}}).png", layerIndexStartNumber, progress, matGenFunc); - - public void DecodeLayersFromZip(ZipArchive zipArchive, IndexStartNumber layerIndexStartNumber = IndexStartNumber.Zero, OperationProgress? progress = null, Func? matGenFunc = null) - => DecodeLayersFromZipRegex(zipArchive, @"^(\d+).png", layerIndexStartNumber, progress, matGenFunc); + => DecodeLayersFromZipRegex(zipArchive, $@"(\d{{{padDigits}}}).png$", layerIndexStartNumber, progress, matGenFunc); public void DecodeLayersFromZip(ZipArchive zipArchive, string prepend, IndexStartNumber layerIndexStartNumber = IndexStartNumber.Zero, OperationProgress? progress = null, Func? matGenFunc = null) - => DecodeLayersFromZipRegex(zipArchive, $@"^{Regex.Escape(prepend)}(\d+).png", layerIndexStartNumber, progress, matGenFunc); + => DecodeLayersFromZipRegex(zipArchive, $@"^{Regex.Escape(prepend)}(\d+).png$", layerIndexStartNumber, progress, matGenFunc); + + public void DecodeLayersFromZip(ZipArchive zipArchive, IndexStartNumber layerIndexStartNumber = IndexStartNumber.Zero, OperationProgress? progress = null, Func? matGenFunc = null) + => DecodeLayersFromZipRegex(zipArchive, @"^(\d+).png$", layerIndexStartNumber, progress, matGenFunc); public void DecodeLayersFromZip(ZipArchive zipArchive, OperationProgress progress, Func? matGenFunc = null) - => DecodeLayersFromZipRegex(zipArchive, @"^(\d+).png", IndexStartNumber.Zero, progress, matGenFunc); + => DecodeLayersFromZipRegex(zipArchive, @"^(\d+).png$", IndexStartNumber.Zero, progress, matGenFunc); + + public void DecodeLayersFromZipIgnoreFilename(ZipArchive zipArchive, IndexStartNumber layerIndexStartNumber = IndexStartNumber.Zero, OperationProgress? progress = null, Func? matGenFunc = null) + { + DecodeLayersFromZipRegex(zipArchive, @$"({@"\d?".Repeat(LayerDigits - 1)}\d).png$", layerIndexStartNumber, progress, matGenFunc); + } + + public void DecodeLayersFromZipIgnoreFilename(ZipArchive zipArchive, OperationProgress progress, Func? matGenFunc = null) + => DecodeLayersFromZipIgnoreFilename(zipArchive, IndexStartNumber.Zero, progress, matGenFunc); /// /// Extract contents to a folder @@ -3823,6 +3865,84 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable + /// Gets the transition layer count calculated from layer exposure time configuration + /// + /// Transition layer count + public ushort ParseTransitionLayerCountFromLayers() + { + ushort count = 0; + for (uint layerIndex = BottomLayerCount + 1u; layerIndex < LayerCount; layerIndex++) + { + if (Math.Abs(this[layerIndex - 1].ExposureTime - this[layerIndex].ExposureTime) < 0.009f) break; // First equal layer, transition ended + count++; + } + + return count; + } + + /// + /// Parse the transition step time from layers, value is returned as positive from normal perspective and logic (Longer - shorter) + /// + /// Seconds + public float ParseTransitionStepTimeFromLayers() + { + var transitionLayerCount = ParseTransitionLayerCountFromLayers(); + return transitionLayerCount == 0 + ? 0 + : (float)Math.Round(this[BottomLayerCount].ExposureTime - this[BottomLayerCount + 1].ExposureTime, 2); + } + + /// + /// Gets the transition step time from a long and short exposure time, value is returned as positive from normal perspective and logic (Longer - shorter) + /// + /// The long exposure time + /// The small exposure time + /// Number of transition layers + /// Seconds + public static float GetTransitionStepTime(float longExposureTime, float shortExposureTime, ushort transitionLayerCount) + { + return transitionLayerCount == 0 ? 0 : (float)Math.Round((longExposureTime - shortExposureTime) / (transitionLayerCount + 1), 2, MidpointRounding.AwayFromZero); + } + + /// + /// Gets the transition step time from and , value is returned as positive from normal perspective and logic (Longer - shorter) + /// + /// Number of transition layers + /// Seconds + public float GetTransitionStepTime(ushort transitionLayerCount) + { + return GetTransitionStepTime(BottomExposureTime, ExposureTime, transitionLayerCount); + } + + /// + /// Gets the transition layer count based on long and short exposure time + /// + /// The long exposure time + /// The small exposure time + /// Decrement time + /// Midpoint rounding method + /// + public static ushort GetTransitionLayerCount(float longExposureTime, float shortExposureTime, float decrementTime, MidpointRounding rounding = MidpointRounding.AwayFromZero) + { + return decrementTime == 0 ? (ushort)0 : (ushort)Math.Round((longExposureTime - shortExposureTime) / decrementTime - 1, rounding); + } + + /// + /// Gets the transition layer count based on and + /// + /// Step decrement time in seconds + /// True if transition layer count can't be higher than supported by the file, otherwise set to false to not look at possible file layers + /// Midpoint rounding method + /// Transition layer count + public ushort GetTransitionLayerCount(float stepDecrementTime, bool constrainToLayerCount = true, MidpointRounding rounding = MidpointRounding.AwayFromZero) + { + var count = GetTransitionLayerCount(BottomExposureTime, ExposureTime, stepDecrementTime, rounding); + if (constrainToLayerCount) count = (ushort)Math.Min(count, MaximumPossibleTransitionLayerCount); + return count; + } + + /// /// Re-set exposure time to the transition layers /// @@ -3845,7 +3965,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable= 2) // Begin new layer { var layerIndex = uint.Parse(match.Groups[1].Value); @@ -965,12 +966,12 @@ public class GCodeBuilder : BindableBase } // Propagate values before switch to the new layer - if (_gCodeShowImagePosition == GCodeShowImagePositions.FirstLine && layerBlock.LayerIndex != layerIndex) + if (/*_gCodeShowImagePosition == GCodeShowImagePositions.FirstLine && */layerBlock.LayerIndex != layerIndex) { layerBlock.SetLayer(true); + layerBlock.LayerIndex = layerIndex; } - layerBlock.LayerIndex = layerIndex; continue; } @@ -981,18 +982,13 @@ public class GCodeBuilder : BindableBase // Check moves if (line.StartsWith(CommandMoveG0.Command) || line.StartsWith(CommandMoveG1.Command)) { - var moveG0Regex = Regex.Match(line, - CommandMoveG0.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(([0-9]*[.])?[0-9]+)"), - RegexOptions.IgnoreCase); - var moveG1Regex = Regex.Match(line, - CommandMoveG1.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(([0-9]*[.])?[0-9]+)"), - RegexOptions.IgnoreCase); - match = moveG0Regex.Success && moveG0Regex.Groups.Count >= 2 ? moveG0Regex : moveG1Regex; - - if (match.Success && match.Groups.Count >= 4 && !layerBlock.RetractSpeed2.HasValue && layerBlock.LightOffCount < 2) + match = Regex.Match(line, $"{CommandMoveG0.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(([0-9]*[.])?[0-9]+)")}|{CommandMoveG1.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(([0-9]*[.])?[0-9]+)")}", RegexOptions.IgnoreCase); + + if (match.Success && match.Groups.Count >= 9 && !layerBlock.RetractSpeed2.HasValue && layerBlock.LightOffCount < 2) { - float pos = float.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); - float speed = ConvertToMillimetersPerMinute(float.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture)); + var startIndex = match.Groups[1].Value.Length > 0 ? 0 : 4; + float pos = float.Parse(match.Groups[startIndex+1].Value, CultureInfo.InvariantCulture); + float speed = ConvertToMillimetersPerMinute(float.Parse(match.Groups[startIndex+3].Value, CultureInfo.InvariantCulture)); layerBlock.Movements.Add(new(pos, speed)); @@ -1037,12 +1033,14 @@ public class GCodeBuilder : BindableBase // Check for waits if (line.StartsWith(CommandWaitG4.Command)) { - match = Regex.Match(line, CommandWaitG4.ToStringWithoutComments(@"(([0-9]*[.])?[0-9]+)"), - RegexOptions.IgnoreCase); + match = Regex.Match(line, CommandWaitG4.ToStringWithoutComments(@"(([0-9]*[.])?[0-9]+)"), RegexOptions.IgnoreCase); if (match.Success && match.Groups.Count >= 2) { - if (/*CommandWaitSyncDelay.Enabled && */match.Groups[1].Value.StartsWith('0')) continue; // Sync movement delay, skip - + if (/*CommandWaitSyncDelay.Enabled && */match.Groups[1].Value.StartsWith('0')) + { + layerBlock.WaitSyncDelayDetected = true; + continue; // Sync movement delay, skip + } var waitTime = float.Parse(match.Groups[1].Value); if (layerBlock.PositionZ.HasValue && @@ -1091,8 +1089,7 @@ public class GCodeBuilder : BindableBase // Check LightPWM if (line.StartsWith(CommandTurnLEDM106.Command)) { - match = Regex.Match(line, CommandTurnLEDM106.ToStringWithoutComments(@"(\d+)"), - RegexOptions.IgnoreCase); + match = Regex.Match(line, CommandTurnLEDM106.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase); if (match.Success && match.Groups.Count >= 2) { byte pwm; diff --git a/UVtools.Core/GCode/GCodeLayer.cs b/UVtools.Core/GCode/GCodeLayer.cs index 400b562..29181e3 100644 --- a/UVtools.Core/GCode/GCodeLayer.cs +++ b/UVtools.Core/GCode/GCodeLayer.cs @@ -43,6 +43,8 @@ public class GCodeLayer public float PreviousPositionZ { get; set; } + public bool WaitSyncDelayDetected { get; set; } + public float? WaitTimeBeforeCure { get => _waitTimeBeforeCure; @@ -118,6 +120,7 @@ public class GCodeLayer public byte LightOffCount { get; set; } public bool IsAfterLightOff => LightOffCount > 0; + public GCodeLayer(FileFormat slicerFile) { @@ -299,7 +302,7 @@ public class GCodeLayer layer.RetractSpeed2 = RetractSpeed2 ?? SlicerFile.GetBottomOrNormalValue(layer, SlicerFile.BottomRetractSpeed2, SlicerFile.RetractSpeed2); layer.LightPWM = LightPWM ?? 0;//SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLightPWM, SlicerFile.LightPWM); - if (SlicerFile.GCode!.CommandWaitSyncDelay.Enabled) // Dirty fix of the value + if (SlicerFile.GCode!.CommandWaitSyncDelay.Enabled && !WaitSyncDelayDetected) // Dirty fix of the value { var syncTime = OperationCalculator.LightOffDelayC.CalculateSeconds(layer, 1.5f); if (syncTime < layer.WaitTimeBeforeCure) diff --git a/UVtools.Core/Managers/SuggestionManager.cs b/UVtools.Core/Managers/SuggestionManager.cs index af7c7c7..cd6b272 100644 --- a/UVtools.Core/Managers/SuggestionManager.cs +++ b/UVtools.Core/Managers/SuggestionManager.cs @@ -26,9 +26,11 @@ public class SuggestionManager public static string FilePath => Path.Combine(CoreSettings.DefaultSettingsFolderAndEnsureCreation, "suggestions.xml"); public SuggestionBottomLayerCount BottomLayerCount { get; set; } = new(); + public SuggestionTransitionLayerCount TransitionLayerCount { get; set; } = new(); public SuggestionWaitTimeBeforeCure WaitTimeBeforeCure { get; set; } = new(); public SuggestionWaitTimeAfterCure WaitTimeAfterCure { get; set; } = new(); public SuggestionLayerHeight LayerHeight { get; set; } = new(); + public SuggestionModelPosition ModelPosition { get; set; } = new(); /// /// Gets all suggestions @@ -41,9 +43,11 @@ public class SuggestionManager return new Suggestion[] { BottomLayerCount, + TransitionLayerCount, WaitTimeBeforeCure, WaitTimeAfterCure, - LayerHeight + LayerHeight, + ModelPosition, }; } set @@ -64,6 +68,9 @@ public class SuggestionManager case SuggestionBottomLayerCount bottomLayerCount: BottomLayerCount = bottomLayerCount; break; + case SuggestionTransitionLayerCount transitionLayerCount: + TransitionLayerCount = transitionLayerCount; + break; case SuggestionWaitTimeBeforeCure waitTimeBeforeCure: WaitTimeBeforeCure = waitTimeBeforeCure; break; @@ -73,6 +80,9 @@ public class SuggestionManager case SuggestionLayerHeight layerHeight: LayerHeight = layerHeight; break; + case SuggestionModelPosition modelPosition: + ModelPosition = modelPosition; + break; default: throw new ArgumentOutOfRangeException(nameof(suggestion)); } } diff --git a/UVtools.Core/Network/MappedDevice.cs b/UVtools.Core/Network/MappedDevice.cs index 87c60cb..582a23e 100644 --- a/UVtools.Core/Network/MappedDevice.cs +++ b/UVtools.Core/Network/MappedDevice.cs @@ -62,7 +62,7 @@ public class MappedDevice : BindableBase #endregion - #region Contructors + #region Constructors public MappedDevice() { } diff --git a/UVtools.Core/Network/RemotePrinterRequest.cs b/UVtools.Core/Network/RemotePrinterRequest.cs index 2894885..1936944 100644 --- a/UVtools.Core/Network/RemotePrinterRequest.cs +++ b/UVtools.Core/Network/RemotePrinterRequest.cs @@ -97,7 +97,7 @@ public class RemotePrinterRequest : BindableBase #endregion - #region Contructors + #region Constructors public RemotePrinterRequest() { } diff --git a/UVtools.Core/Objects/MappedProcess.cs b/UVtools.Core/Objects/MappedProcess.cs index addfe3f..9abe4d1 100644 --- a/UVtools.Core/Objects/MappedProcess.cs +++ b/UVtools.Core/Objects/MappedProcess.cs @@ -89,7 +89,7 @@ public class MappedProcess : BindableBase #endregion - #region Contructors + #region Constructors public MappedProcess() { } diff --git a/UVtools.Core/Operations/OperationFadeExposureTime.cs b/UVtools.Core/Operations/OperationFadeExposureTime.cs index 8ea6b08..e0715e3 100644 --- a/UVtools.Core/Operations/OperationFadeExposureTime.cs +++ b/UVtools.Core/Operations/OperationFadeExposureTime.cs @@ -133,7 +133,7 @@ public class OperationFadeExposureTime : Operation } public decimal IncrementValue => Math.Round(IncrementValueRaw, 2); - public decimal IncrementValueRaw => (_toExposureTime - _fromExposureTime) / (LayerRangeCount + 1); + public decimal IncrementValueRaw => (decimal)FileFormat.GetTransitionStepTime((float) _toExposureTime, (float)_fromExposureTime, (ushort) LayerRangeCount); #endregion @@ -182,9 +182,16 @@ public class OperationFadeExposureTime : Operation { LayerIndexEnd = LayerIndexStart + _layerCount - 1; // Sanitize - if (_disableFirmwareTransitionLayers) + if (SlicerFile.TransitionLayerType == FileFormat.TransitionLayerTypes.Firmware && _disableFirmwareTransitionLayers) { - SlicerFile.TransitionLayerCount = 0; + SlicerFile.TransitionLayerCount = 0; + } + else + { + if (SlicerFile.BottomLayerCount == LayerIndexStart) + { + SlicerFile.TransitionLayerCount = (ushort) LayerRangeCount; + } } var increment = IncrementValueRaw; diff --git a/UVtools.Core/Suggestions/Suggestion.cs b/UVtools.Core/Suggestions/Suggestion.cs index 613a3ee..ae44626 100644 --- a/UVtools.Core/Suggestions/Suggestion.cs +++ b/UVtools.Core/Suggestions/Suggestion.cs @@ -11,6 +11,7 @@ using System.ComponentModel; using System.Xml.Serialization; using UVtools.Core.FileFormats; using UVtools.Core.Objects; +using UVtools.Core.Operations; namespace UVtools.Core.Suggestions; @@ -151,9 +152,10 @@ public abstract class Suggestion : BindableBase /// /// Executes and applies the suggestion /// + /// /// /// - protected virtual bool ExecuteInternally() + protected virtual bool ExecuteInternally(OperationProgress progress) { throw new NotImplementedException(); } @@ -163,12 +165,15 @@ public abstract class Suggestion : BindableBase /// /// /// - public bool Execute() + public bool Execute(OperationProgress? progress = null) { if (_slicerFile is null) throw new InvalidOperationException($"The suggestion '{Title}' can't execute due the lacking of a file parent."); if (!Enabled || !IsAvailable || IsApplied || IsInformativeOnly || SlicerFile.LayerCount == 0) return false; - - var result = ExecuteInternally(); + + progress ??= new OperationProgress(); + progress.Title = $"Applying suggestion: {Title}"; + + var result = ExecuteInternally(progress); RaisePropertyChanged(nameof(IsApplied)); RefreshNotifyMessage(); diff --git a/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs b/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs index 28c3c77..2a244f4 100644 --- a/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs +++ b/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs @@ -9,6 +9,7 @@ using System; using System.Text; using UVtools.Core.Layers; +using UVtools.Core.Operations; namespace UVtools.Core.Suggestions; @@ -48,7 +49,7 @@ public sealed class SuggestionBottomLayerCount : Suggestion } } - public override string Title => "Bottom layer count"; + public override string Title => "Bottom layers"; public override string Description => "Bottom layers should be kept to a minimum, usually from 2 to 3, it function is to provide a good adhesion to the first layer on the build plate, using a high count have disadvantages."; @@ -56,7 +57,7 @@ public sealed class SuggestionBottomLayerCount : Suggestion ? $"{GlobalAppliedMessage}: {SlicerFile.BottomLayerCount} / {SlicerFile.BottomLayersHeight}mm" : $"{GlobalNotAppliedMessage} ({SlicerFile.BottomLayerCount}) is out of the recommended {BottomLayerCountValue} layers"; - public override string ToolTip => $"The recommended total height for the bottom layers must be between {_minimumBottomHeight}mm and {_maximumBottomHeight}mm constrained from {_minimumBottomLayerCount} to {_maximumBottomLayerCount} layers.\n" + + public override string ToolTip => $"The recommended total height for the bottom layers must be between [{_minimumBottomHeight}mm to {_maximumBottomHeight}mm] constrained from [{_minimumBottomLayerCount} to {_maximumBottomLayerCount}] layers.\n" + $"Explanation: {Description}"; public override string? InformationUrl => "https://ameralabs.com/blog/default-3d-printing-raft-settings"; @@ -66,19 +67,19 @@ public sealed class SuggestionBottomLayerCount : Suggestion public decimal TargetBottomHeight { get => _targetBottomHeight; - set => RaiseAndSetIfChanged(ref _targetBottomHeight, Layer.RoundHeight(value)); + set => RaiseAndSetIfChanged(ref _targetBottomHeight, Layer.RoundHeight(Math.Max(0, value))); } public decimal MinimumBottomHeight { get => _minimumBottomHeight; - set => RaiseAndSetIfChanged(ref _minimumBottomHeight, Layer.RoundHeight(value)); + set => RaiseAndSetIfChanged(ref _minimumBottomHeight, Layer.RoundHeight(Math.Max(0, value))); } public decimal MaximumBottomHeight { get => _maximumBottomHeight; - set => RaiseAndSetIfChanged(ref _maximumBottomHeight, Layer.RoundHeight(value)); + set => RaiseAndSetIfChanged(ref _maximumBottomHeight, Layer.RoundHeight(Math.Max(0, value))); } public byte MinimumBottomLayerCount @@ -103,6 +104,21 @@ public sealed class SuggestionBottomLayerCount : Suggestion { var sb = new StringBuilder(); + if (_targetBottomHeight < 0) + { + sb.AppendLine("Bottom height must be a positive value"); + } + + if (_minimumBottomHeight < 0) + { + sb.AppendLine("Minimum limit (mm) must be a positive value"); + } + + if (_maximumBottomHeight < 0) + { + sb.AppendLine("maximum limit (mm) must be a positive value"); + } + if (_minimumBottomHeight > _maximumBottomHeight) { sb.AppendLine("Minimum limit (mm) can't be higher than maximum limit (mm)"); @@ -118,7 +134,7 @@ public sealed class SuggestionBottomLayerCount : Suggestion #endregion - #region Contructor + #region Constructor public SuggestionBottomLayerCount() { _applyWhen = SuggestionApplyWhen.OutsideLimits; @@ -127,7 +143,7 @@ public sealed class SuggestionBottomLayerCount : Suggestion #region Methods - protected override bool ExecuteInternally() + protected override bool ExecuteInternally(OperationProgress progress) { SlicerFile.BottomLayerCount = BottomLayerCountValue; return true; diff --git a/UVtools.Core/Suggestions/SuggestionLayerHeight.cs b/UVtools.Core/Suggestions/SuggestionLayerHeight.cs index 884d32c..f5fda81 100644 --- a/UVtools.Core/Suggestions/SuggestionLayerHeight.cs +++ b/UVtools.Core/Suggestions/SuggestionLayerHeight.cs @@ -56,7 +56,7 @@ public sealed class SuggestionLayerHeight : Suggestion ? $"{GlobalAppliedMessage}: {SlicerFile.LayerHeight}mm" : $"{GlobalNotAppliedMessage} is out of the recommended {_minimumLayerHeight}mm » {_maximumLayerHeight}mm, up to {_maximumLayerHeightDecimalPlates} decimal digit(s)"; - public override string ToolTip => $"The recommended layer height is between {_minimumLayerHeight}mm and {_maximumLayerHeight}mm up to {_maximumLayerHeightDecimalPlates} digit(s) precision.\n" + + public override string ToolTip => $"The recommended layer height is between [{_minimumLayerHeight}mm to {_maximumLayerHeight}mm] up to {_maximumLayerHeightDecimalPlates} digit(s) precision.\n" + $"Explanation: {Description}"; public override string? ConfirmationMessage => $"{Title}: Re-slice the model with proper layer height"; @@ -97,6 +97,11 @@ public sealed class SuggestionLayerHeight : Suggestion sb.AppendLine("Minimum layer height must be higher than 0mm"); } + if (_maximumLayerHeight <= 0) + { + sb.AppendLine("Maximum layer height must be higher than 0mm"); + } + if (_minimumLayerHeight > _maximumLayerHeight) { sb.AppendLine("Minimum layer height can't be higher than maximum layer height"); diff --git a/UVtools.Core/Suggestions/SuggestionModelPosition.cs b/UVtools.Core/Suggestions/SuggestionModelPosition.cs new file mode 100644 index 0000000..a9250ed --- /dev/null +++ b/UVtools.Core/Suggestions/SuggestionModelPosition.cs @@ -0,0 +1,290 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using UVtools.Core.Operations; + +namespace UVtools.Core.Suggestions; + +public sealed class SuggestionModelPosition : Suggestion +{ + #region Enums + public enum SuggestionModelAnchor : byte + { + [Description("⬚ Random")] + Random, + [Description("⌜ Top left")] + TopLeft, + [Description("⌝ Top right")] + TopRight, + [Description("⌟ Bottom right")] + BottomRight, + [Description("⌞ Bottom left")] + BottomLeft + } + #endregion + + #region Members + + private ushort _targetTopBottomMargin = 100; + private ushort _targetLeftRightMargin = 100; + private ushort _minimumTopBottomMargin = 50; + private ushort _maximumTopBottomMargin = 300; + private ushort _minimumLeftRightMargin = 50; + private ushort _maximumLeftRightMargin = 300; + private SuggestionModelAnchor _anchorType = SuggestionModelAnchor.Random; + + #endregion + + #region Properties + + public override bool IsApplied + { + get + { + if (SlicerFile is null) return false; + + if (SlicerFile.BoundingRectangle.Size == SlicerFile.Resolution) return true; + + var topLeft = new Point(LeftRightMargin, TopBottomMargin); + var topRight = new Point((int)SlicerFile.ResolutionX - LeftRightMargin, TopBottomMargin); + var bottomRight = new Point((int)SlicerFile.ResolutionX - LeftRightMargin, (int)SlicerFile.ResolutionY - TopBottomMargin); + var bottomLeft = new Point(LeftRightMargin, (int)SlicerFile.ResolutionY - TopBottomMargin); + + var applyWhen = _applyWhen; + if (SlicerFile.ResolutionX - SlicerFile.BoundingRectangle.Size.Width < _minimumLeftRightMargin * 2 + || SlicerFile.ResolutionY - SlicerFile.BoundingRectangle.Size.Height < _minimumTopBottomMargin * 2) + { + applyWhen = SuggestionApplyWhen.Different; + } + + return applyWhen switch + { + SuggestionApplyWhen.OutsideLimits => + /*TL*/ (SlicerFile.BoundingRectangle.X >= _minimumLeftRightMargin && SlicerFile.BoundingRectangle.X <= _maximumLeftRightMargin && SlicerFile.BoundingRectangle.Y >= _minimumTopBottomMargin && SlicerFile.BoundingRectangle.Y <= _maximumTopBottomMargin) + /*TR*/ || (SlicerFile.BoundingRectangle.Right <= (int)SlicerFile.ResolutionX - _minimumLeftRightMargin && SlicerFile.BoundingRectangle.Right >= (int)SlicerFile.ResolutionX - _maximumLeftRightMargin && SlicerFile.BoundingRectangle.Y >= _minimumTopBottomMargin && SlicerFile.BoundingRectangle.Y <= _maximumTopBottomMargin) + /*BR*/ || (SlicerFile.BoundingRectangle.Right <= (int)SlicerFile.ResolutionX - _minimumLeftRightMargin && SlicerFile.BoundingRectangle.Right >= (int)SlicerFile.ResolutionX - _maximumLeftRightMargin && SlicerFile.BoundingRectangle.Bottom <= (int)SlicerFile.ResolutionY - _minimumTopBottomMargin && SlicerFile.BoundingRectangle.Bottom >= (int)SlicerFile.ResolutionY - _maximumTopBottomMargin) + /*BL*/ || (SlicerFile.BoundingRectangle.X >= _minimumLeftRightMargin && SlicerFile.BoundingRectangle.X <= _maximumLeftRightMargin && SlicerFile.BoundingRectangle.Bottom <= (int)SlicerFile.ResolutionY - _minimumTopBottomMargin && SlicerFile.BoundingRectangle.Bottom >= (int)SlicerFile.ResolutionY - _maximumTopBottomMargin) + , + SuggestionApplyWhen.Different => + /*TL*/ (SlicerFile.BoundingRectangle.Location == topLeft) + /*TR*/ || (SlicerFile.BoundingRectangle.Right == topRight.X && SlicerFile.BoundingRectangle.Y == topRight.Y) + /*BR*/ || (SlicerFile.BoundingRectangle.Right == bottomRight.X && SlicerFile.BoundingRectangle.Bottom == bottomRight.Y) + /*BL*/ || (SlicerFile.BoundingRectangle.X == bottomLeft.X && SlicerFile.BoundingRectangle.Bottom == bottomLeft.Y) + , + _ => throw new ArgumentOutOfRangeException() + }; + } + } + + public override string Title => "Model position"; + + public override string Description => + "Printing on a corner will reduce the FEP stretch forces when detaching from the model during a lift sequence, benefits are: Reduced lift height and faster printing, less stretch, less FEP marks, better FEP lifespan, easier to peel, less prone to failure and use the screen pixels more evenly.\n" + + "If the model is too large to fit within the margin(s) on the screen, it will attempt to center it on that same axis to avoid touching on screen edge(s) and to give a sane margin from it."; + + public override string Message => IsApplied + ? $"{GlobalAppliedMessage}: {SlicerFile.BoundingRectangle.ToString().Replace(",", ", ")}" + : $"{GlobalNotAppliedMessage}: {SlicerFile.BoundingRectangle.Location} is out of the recommended {AnchorPosition}"; + + public override string ToolTip => $"The recommended model position must be at a corner and between a top/bottom margin of [{_minimumTopBottomMargin}px to {_maximumTopBottomMargin}px] and left/right margin of [{_minimumLeftRightMargin}px to {_maximumLeftRightMargin}px].\n" + + $"Explanation: {Description}"; + + public override string? InformationUrl => "https://ameralabs.com/blog/9-settings-to-change-for-faster-resin-3d-printing"; + + public override string? ConfirmationMessage => $"{Title}: {SlicerFile.BoundingRectangle.Location} » {AnchorPosition} ({AnchorType})"; + + public SuggestionModelAnchor AnchorType + { + get => _anchorType; + set => RaiseAndSetIfChanged(ref _anchorType, value); + } + + public ushort TargetTopBottomMargin + { + get => _targetTopBottomMargin; + set => RaiseAndSetIfChanged(ref _targetTopBottomMargin, value); + } + + public ushort TopBottomMargin + { + get + { + var margin = Math.Clamp(_targetTopBottomMargin, _minimumTopBottomMargin, _maximumTopBottomMargin); + return SlicerFile.ResolutionY - SlicerFile.BoundingRectangle.Size.Height < margin * 2 + ? (ushort) Math.Round((SlicerFile.ResolutionY - SlicerFile.BoundingRectangle.Size.Height) / 2f, + MidpointRounding.AwayFromZero) + : margin; + } + } + + + public ushort TargetLeftRightMargin + { + get => _targetLeftRightMargin; + set => RaiseAndSetIfChanged(ref _targetLeftRightMargin, value); + } + + public ushort LeftRightMargin + { + get + { + var margin = Math.Clamp(_targetLeftRightMargin, _minimumLeftRightMargin, _maximumLeftRightMargin); + return SlicerFile.ResolutionX - SlicerFile.BoundingRectangle.Size.Width < margin * 2 + ? (ushort) Math.Round((SlicerFile.ResolutionX - SlicerFile.BoundingRectangle.Size.Width) / 2f, + MidpointRounding.AwayFromZero) + : margin; + } + } + + public ushort MinimumTopBottomMargin + { + get => _minimumTopBottomMargin; + set => RaiseAndSetIfChanged(ref _minimumTopBottomMargin, value); + } + + public ushort MaximumTopBottomMargin + { + get => _maximumTopBottomMargin; + set => RaiseAndSetIfChanged(ref _maximumTopBottomMargin, value); + } + + public ushort MinimumLeftRightMargin + { + get => _minimumLeftRightMargin; + set => RaiseAndSetIfChanged(ref _minimumLeftRightMargin, value); + } + + public ushort MaximumLeftRightMargin + { + get => _maximumLeftRightMargin; + set => RaiseAndSetIfChanged(ref _maximumLeftRightMargin, value); + } + + public Anchor ToolMoveAnchor + { + get + { + switch (_anchorType) + { + case SuggestionModelAnchor.TopLeft: + return Anchor.TopLeft; + case SuggestionModelAnchor.TopRight: + return Anchor.TopRight; + case SuggestionModelAnchor.BottomRight: + return Anchor.BottomRight; + case SuggestionModelAnchor.BottomLeft: + return Anchor.BottomLeft; + case SuggestionModelAnchor.Random: + default: + var anchors = new[] + { + Anchor.TopLeft, + Anchor.TopRight, + Anchor.BottomRight, + Anchor.BottomLeft + }; + + return anchors[new Random().Next(anchors.Length)]; + } + } + } + + public Point AnchorPosition => + ToolMoveAnchor switch + { + Anchor.TopLeft => new Point(LeftRightMargin, TopBottomMargin), + Anchor.TopRight => new Point((int) SlicerFile.ResolutionX - LeftRightMargin, TopBottomMargin), + Anchor.BottomRight => new Point((int) SlicerFile.ResolutionX - LeftRightMargin, (int) SlicerFile.ResolutionY - TopBottomMargin), + Anchor.BottomLeft => new Point(LeftRightMargin, (int) SlicerFile.ResolutionY - TopBottomMargin), + _ => throw new ArgumentOutOfRangeException() + }; + + /*private static Anchor ToolMoveRandomAnchor + { + get + { + var anchors = new[] + { + Anchor.TopLeft, + Anchor.TopRight, + Anchor.BottomRight, + Anchor.BottomLeft + }; + + return anchors[new Random().Next(0, anchors.Length)]; + } + }*/ + + #endregion + + #region Override + + public override string? Validate() + { + var sb = new StringBuilder(); + + if (_minimumTopBottomMargin > _maximumTopBottomMargin) + { + sb.AppendLine("Minimum top/bottom margin limit (px) can't be higher than maximum limit (px)"); + } + + if (_minimumLeftRightMargin > _maximumLeftRightMargin) + { + sb.AppendLine("Minimum left/right margin limit (px) can't be higher than maximum limit (px)"); + } + + return sb.ToString(); + } + + #endregion + + #region Constructor + public SuggestionModelPosition() + { + _applyWhen = SuggestionApplyWhen.OutsideLimits; + } + #endregion + + #region Methods + + protected override bool ExecuteInternally(OperationProgress progress) + { + var anchor = ToolMoveAnchor; + var operation = new OperationMove(SlicerFile, anchor); + + switch (anchor) + { + case Anchor.TopLeft: + operation.MarginLeft = LeftRightMargin; + operation.MarginTop = TopBottomMargin; + break; + case Anchor.TopRight: + operation.MarginRight = LeftRightMargin; + operation.MarginTop = TopBottomMargin; + break; + case Anchor.BottomLeft: + operation.MarginLeft = LeftRightMargin; + operation.MarginBottom = TopBottomMargin; + break; + case Anchor.BottomRight: + operation.MarginRight = LeftRightMargin; + operation.MarginBottom = TopBottomMargin; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return operation.IsWithinBoundary && operation.Execute(progress); + } + + #endregion +} \ No newline at end of file diff --git a/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs b/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs new file mode 100644 index 0000000..7cc84b5 --- /dev/null +++ b/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs @@ -0,0 +1,193 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Text; +using UVtools.Core.Operations; + +namespace UVtools.Core.Suggestions; + +public sealed class SuggestionTransitionLayerCount : Suggestion +{ + #region Members + + private decimal _transitionStepTime = 2; + private ushort _minimumTransitionLayerCount = 3; + private ushort _maximumTransitionLayerCount = 10; + + #endregion + + #region Properties + + public override bool IsAvailable + { + get + { + if (SlicerFile is null) return false; + return SlicerFile.CanUseLayerExposureTime && SlicerFile.LayerCount != 0; + } + } + + public override bool IsApplied + { + get + { + if (SlicerFile is null) return false; + + // Can't apply + if (SlicerFile.BottomLayerCount == 0 + || SlicerFile.BottomExposureTime <= 0 + || SlicerFile.ExposureTime <= 0 + || SlicerFile.BottomExposureTime < SlicerFile.ExposureTime + || SlicerFile.BottomLayerCount + _minimumTransitionLayerCount > SlicerFile.LayerCount + || SlicerFile.MaximumPossibleTransitionLayerCount < _minimumTransitionLayerCount) return true; + + var actualTransitionLayerCount = SlicerFile.ParseTransitionLayerCountFromLayers(); + var suggestedTransitionLayerCount = TransitionLayerCount; + + if (actualTransitionLayerCount == suggestedTransitionLayerCount) return true; + + return _applyWhen switch + { + SuggestionApplyWhen.OutsideLimits => actualTransitionLayerCount >= _minimumTransitionLayerCount && + actualTransitionLayerCount <= _maximumTransitionLayerCount, + SuggestionApplyWhen.Different => actualTransitionLayerCount == suggestedTransitionLayerCount, + + _ => throw new ArgumentOutOfRangeException() + }; + } + } + + public override string Title => "Transition layers"; + + public override string Description => "If you are printing flat on the build plate your model will print better when using a smooth transition exposure time instead of a harsh variation, resulting in reduced layer line effect and avoid possible problems due the large exposure difference.\n" + + "This is not so important when your model print raised under a raft/supports unaffected by the bottom exposure, in that case, it's fine to ignore this."; + + public override string Message + { + get + { + var actualTransitionDecrementTime = SlicerFile.ParseTransitionStepTimeFromLayers(); + var actualTransitionLayerCount = SlicerFile.ParseTransitionLayerCountFromLayers(); + + var suggestedTransitionLayerCount = TransitionLayerCount; + var suggestedTransitionDecrementTime = SlicerFile.GetTransitionStepTime(suggestedTransitionLayerCount); + + return IsApplied + ? $"{GlobalAppliedMessage}: {SlicerFile.BottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s" + : $"{GlobalNotAppliedMessage} {SlicerFile.BottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s is out of the recommended {SlicerFile.BottomExposureTime}s » {(suggestedTransitionDecrementTime <= 0 || suggestedTransitionLayerCount == 0 ? string.Empty : $"[-{suggestedTransitionDecrementTime}s/{suggestedTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s"; + } + } + + public override string ToolTip => $"The recommended transition time is ±{_transitionStepTime}s constrained over [{_minimumTransitionLayerCount} to {_maximumTransitionLayerCount}] layers.\n" + + $"Explanation: {Description}"; + + public override string? InformationUrl => "https://ameralabs.com/blog/9-settings-to-change-for-faster-resin-3d-printing"; + + public override string? ConfirmationMessage + { + get + { + var actualTransitionDecrementTime = SlicerFile.ParseTransitionStepTimeFromLayers(); + var actualTransitionLayerCount = SlicerFile.ParseTransitionLayerCountFromLayers(); + + var suggestedTransitionLayerCount = TransitionLayerCount; + var suggestedTransitionDecrementTime = SlicerFile.GetTransitionStepTime(suggestedTransitionLayerCount); + + return + $"{Title}: ({SlicerFile.BottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s) » ({SlicerFile.BottomExposureTime}s » {(suggestedTransitionDecrementTime <= 0 || suggestedTransitionLayerCount == 0 ? string.Empty : $"[-{suggestedTransitionDecrementTime}s/{suggestedTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s)"; + } + } + + public ushort TransitionLayerCount => + (ushort)Math.Min( + Math.Clamp( + SlicerFile.GetTransitionLayerCount((float)_transitionStepTime, false), + _minimumTransitionLayerCount, + _maximumTransitionLayerCount) + , SlicerFile.MaximumPossibleTransitionLayerCount); + + public decimal TransitionStepTime + { + get => _transitionStepTime; + set => RaiseAndSetIfChanged(ref _transitionStepTime, Math.Max(0, Math.Round(value, 2))); + } + + public ushort MinimumTransitionLayerCount + { + get => _minimumTransitionLayerCount; + set => RaiseAndSetIfChanged(ref _minimumTransitionLayerCount, value); + } + + public ushort MaximumTransitionLayerCount + { + get => _maximumTransitionLayerCount; + set => RaiseAndSetIfChanged(ref _maximumTransitionLayerCount, value); + } + + #endregion + + #region Override + + public override string? Validate() + { + var sb = new StringBuilder(); + + + if (_transitionStepTime < 0) + { + sb.AppendLine("Decrement time (s) must be zero or a positive value"); + } + + /*if (_minimumTransitionTimeDecrement < 0) + { + sb.AppendLine("Minimum limit (s) can't be a negative value"); + } + + if (_maximumTransitionTimeDecrement < 0) + { + sb.AppendLine("Maximum limit (s) can't be a negative value"); + } + + if (_minimumTransitionTimeDecrement > _maximumTransitionTimeDecrement) + { + sb.AppendLine("Minimum limit (s) can't be higher than maximum limit (s)"); + }*/ + + /*if (_minimumTransitionLayerCount == 0) + { + sb.AppendLine("Minimum limit (layers) must be a positive value"); + }*/ + + if (_minimumTransitionLayerCount > _maximumTransitionLayerCount) + { + sb.AppendLine("Minimum limit (layers) can't be higher than maximum limit (layers)"); + } + + return sb.ToString(); + } + + #endregion + + #region Constructor + public SuggestionTransitionLayerCount() + { + _applyWhen = SuggestionApplyWhen.Different; + } + #endregion + + #region Methods + + protected override bool ExecuteInternally(OperationProgress progress) + { + SlicerFile.TransitionLayerCount = TransitionLayerCount; + return true; + } + + #endregion +} \ No newline at end of file diff --git a/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs b/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs index b34f689..3ba2815 100644 --- a/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs +++ b/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs @@ -10,6 +10,7 @@ using System; using System.ComponentModel; using System.Text; using UVtools.Core.Extensions; +using UVtools.Core.Operations; namespace UVtools.Core.Suggestions; @@ -110,7 +111,7 @@ public sealed class SuggestionWaitTimeAfterCure : Suggestion public override string ToolTip => (_setType == SuggestionWaitTimeAfterCureSetType.Fixed ? $"The recommended wait time must be {_fixedBottomWaitTimeAfterCure}/{_fixedWaitTimeAfterCure}s" : $"The recommended wait time is a ratio of (wait time){_proportionalWaitTimeAfterCure}s to (exposure time){_proportionalExposureTime}s") + - $" constrained from [Bottoms={_minimumBottomWaitTimeAfterCure}/{_maximumBottomWaitTimeAfterCure}s] and [Normals={_minimumWaitTimeAfterCure}/{_maximumWaitTimeAfterCure}s].\n" + + $" constrained from [Bottoms={_minimumBottomWaitTimeAfterCure}s to {_maximumBottomWaitTimeAfterCure}s] and [Normals={_minimumWaitTimeAfterCure}s to {_maximumWaitTimeAfterCure}s].\n" + $"Explanation: {Description}"; public override string? ConfirmationMessage => $"{Title}: {SlicerFile.BottomWaitTimeAfterCure}/{SlicerFile.WaitTimeAfterCure}s » {CalculateWaitTime(true, (decimal)SlicerFile.BottomWaitTimeAfterCure)}/{CalculateWaitTime(false, (decimal)SlicerFile.WaitTimeAfterCure)}s"; @@ -224,7 +225,7 @@ public sealed class SuggestionWaitTimeAfterCure : Suggestion #region Methods - protected override bool ExecuteInternally() + protected override bool ExecuteInternally(OperationProgress progress) { if (SlicerFile.CanUseBottomWaitTimeAfterCure) { diff --git a/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs b/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs index 5c84694..b086481 100644 --- a/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs +++ b/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Text; using UVtools.Core.Extensions; using UVtools.Core.Layers; +using UVtools.Core.Operations; namespace UVtools.Core.Suggestions; @@ -147,7 +148,7 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion public override string ToolTip => (_setType == SuggestionWaitTimeBeforeCureSetType.Fixed ? $"The recommended wait time must be {_fixedBottomWaitTimeBeforeCure}/{_fixedWaitTimeBeforeCure}s" : $"The recommended wait time is a ratio of (wait time){_proportionalWaitTimeBeforeCure}s to (exposure time){_proportionalLayerArea}s") + - $" constrained from [Bottoms={_minimumBottomWaitTimeBeforeCure}/{_maximumBottomWaitTimeBeforeCure}s] and [Normals={_minimumWaitTimeBeforeCure}/{_maximumWaitTimeBeforeCure}s].\n" + + $" constrained from [Bottoms={_minimumBottomWaitTimeBeforeCure}s to {_maximumBottomWaitTimeBeforeCure}s] and [Normals={_minimumWaitTimeBeforeCure}s to {_maximumWaitTimeBeforeCure}s].\n" + $"Explanation: {Description}"; public override string? ConfirmationMessage => $"{Title}: {SlicerFile.BottomWaitTimeAfterCure}/{SlicerFile.WaitTimeAfterCure}s » {CalculateWaitTime(true)}/{CalculateWaitTime(false)}s"; @@ -380,7 +381,7 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion #region Methods - protected override bool ExecuteInternally() + protected override bool ExecuteInternally(OperationProgress progress) { if (SlicerFile.CanUseBottomWaitTimeAfterCure || SlicerFile.CanUseBottomLightOffDelay) { diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 7e4e895..f7e84d7 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, calibration, repair, conversion and manipulation - 3.6.8 + 3.7.0 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 diff --git a/UVtools.Installer/Code/HeatGeneratedFileList.wxs b/UVtools.Installer/Code/HeatGeneratedFileList.wxs index 460b04c..76232c5 100644 --- a/UVtools.Installer/Code/HeatGeneratedFileList.wxs +++ b/UVtools.Installer/Code/HeatGeneratedFileList.wxs @@ -323,8 +323,8 @@ - - + + @@ -1685,7 +1685,7 @@ - + diff --git a/UVtools.Installer/Code/Product.wxs b/UVtools.Installer/Code/Product.wxs index 1e90877..b094293 100644 --- a/UVtools.Installer/Code/Product.wxs +++ b/UVtools.Installer/Code/Product.wxs @@ -9,16 +9,35 @@ is a seamless uninstall/reinstall. Version="$(var.MSIProductVersion)" --> - + + + + - + + + + + + + + + + + NOT WIX_DOWNGRADE_DETECTED + +