From 925808e110961bf102d05c6665e375e73ff84f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Wed, 14 Dec 2022 23:33:04 +0000 Subject: [PATCH] v3.9.3 - **Issues:** - (Fix) Resin traps and suction cups area sum, was losing precision due uint cast (#621) - (Fix) Overhang area was incorrectly showing bounding rectangle area instead it real area - (Fix) OSF: Unable to open certain files when using anti-aliasing - (Upgrade) .NET from 6.0.11 to 6.0.12 --- CHANGELOG.md | 8 + CREDITS.md | 3 +- RELEASE_NOTES.md | 10 +- Scripts/010 Editor/osf.bt | 48 ++++- UVtools.Core/FileFormats/FileFormat.cs | 13 ++ UVtools.Core/FileFormats/OSFFile.cs | 66 ++++--- UVtools.Core/Layers/MainIssue.cs | 11 +- UVtools.Core/UVtools.Core.csproj | 4 +- .../Code/HeatGeneratedFileList.wxs | 6 +- UVtools.ScriptSample/ScriptBuildGrid.cs | 119 ++++++++++++ .../ScriptCompensateCrossBleeding.cs | 86 +++++++++ .../ScriptPreventResinShrinkage.cs | 175 ++++++++++++++++++ UVtools.WPF/UVtools.WPF.csproj | 8 +- documentation/UVtools.Core.xml | 8 + 14 files changed, 514 insertions(+), 51 deletions(-) create mode 100644 UVtools.ScriptSample/ScriptBuildGrid.cs create mode 100644 UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs create mode 100644 UVtools.ScriptSample/ScriptPreventResinShrinkage.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 771b093..e54911f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## /12/2022 - v3.9.3 + +- **Issues:** + - (Fix) Resin traps and suction cups area sum, was losing precision due uint cast (#621) + - (Fix) Overhang area was incorrectly showing bounding rectangle area instead it real area +- (Fix) OSF: Unable to open certain files when using anti-aliasing +- (Upgrade) .NET from 6.0.11 to 6.0.12 + ## 11/12/2022 - v3.9.2 - **Dynamic lifts:** diff --git a/CREDITS.md b/CREDITS.md index e0cd610..ca6d755 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -75,4 +75,5 @@ - Ajilus - James F Hammond - Steven Woodward -- Piotr Czerkasow \ No newline at end of file +- Piotr Czerkasow +- Kevin Bullmann \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e800c31..2b5bf69 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ -- **Dynamic lifts:** - - (Improvement) Remove the 'light-off delay' set logic as it is now better integrated on 'Wait time before cure' suggestion and should run/applied there if intended - - (Fix) Prevent set NaN values to lift height and/or speed - - (Fix) Lift height and speed for the current layer must be calculated from previous layer (#618) -- (Improvement) Windows auto-updater will install the update without dialogs, just displaying installation progress +- **Issues:** + - (Fix) Resin traps and suction cups area sum, was losing precision due uint cast (#621) + - (Fix) Overhang area was incorrectly showing bounding rectangle area instead it real area +- (Fix) OSF: Unable to open certain files when using anti-aliasing +- (Upgrade) .NET from 6.0.11 to 6.0.12 diff --git a/Scripts/010 Editor/osf.bt b/Scripts/010 Editor/osf.bt index 3886887..0c8ebf4 100644 --- a/Scripts/010 Editor/osf.bt +++ b/Scripts/010 Editor/osf.bt @@ -129,17 +129,51 @@ struct LAYER_DEF { local long currentPos = FTell(); local long rleSize = 0; - while(!FEof()) + if (NumberOfPixels > 0) { - if(ReadByte() == 0x0D && (ReadByte(FTell()+1) == 0x0A || ReadByte(FTell()+1) == 0x0B)) + local byte buffer; + local byte slen; + while(!FEof()) { - break; + buffer = ReadByte(); + if ((buffer & 0x01) == 0x01) // It's a run + { + FSkip(1); + slen = ReadByte(); + if (buffer == 0x0D && (slen == 0x0A || slen == 0x0B)) + { + break; + } + + FSkip(1); + rleSize += 2; + + if ((slen & 0x80) == 0) {} + else if ((slen & 0xc0) == 0x80) + { + rleSize++; + FSkip(1); + } + else if ((slen & 0xe0) == 0xc0) + { + rleSize += 2; + FSkip(2); + } + else if ((slen & 0xf0) == 0xe0) + { + rleSize += 3; + FSkip(3); + } + } + else + { + rleSize++; + FSkip(1); + } } - rleSize++; - FSkip(1); + FSeek(currentPos); + ubyte RLE[rleSize] ; } - FSeek(currentPos); - ubyte RLE[rleSize] ; }; diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index effe3e1..8185462 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -5189,6 +5189,19 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable + /// Converts millimeters to pixels given the current resolution and display size + /// + /// Millimeters to convert + /// Fallback to this value in pixels if no ratio is available to make the convertion + /// Pixels + public float MillimetersToPixelsF(float millimeters, uint fallbackToPixels = 0) + { + var ppmm = PpmmMax; + if (ppmm <= 0) return fallbackToPixels; + return ppmm * millimeters; + } + /// /// From a pixel position get the equivalent position on the display /// diff --git a/UVtools.Core/FileFormats/OSFFile.cs b/UVtools.Core/FileFormats/OSFFile.cs index 028fb8d..8009dc8 100644 --- a/UVtools.Core/FileFormats/OSFFile.cs +++ b/UVtools.Core/FileFormats/OSFFile.cs @@ -7,13 +7,13 @@ */ using BinarySerialization; -using Emgu.CV; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Threading.Tasks; +using Emgu.CV; using UVtools.Core.Extensions; using UVtools.Core.Layers; using UVtools.Core.Objects; @@ -250,6 +250,7 @@ public class OSFFile : FileFormat internal Mat DecodeImage(OSFFile parent) { var mat = parent.CreateMat(); + if (NumberOfPixels == 0) return mat; int pixel = (int)(StartY * parent.ResolutionX); for (var n = 0; n < EncodedRle.Length; n++) @@ -260,9 +261,7 @@ public class OSFFile : FileFormat if ((code & 0x01) == 0x01) // It's a run { code &= 0xfe; // Get the grey value - n++; - - var slen = EncodedRle[n]; + var slen = EncodedRle[++n]; if ((slen & 0x80) == 0) { @@ -785,9 +784,9 @@ public class OSFFile : FileFormat { inputFile.Seek(Header.HeaderLength, SeekOrigin.Begin); var layerDef = new OSFLayerDef[LayerCount]; - int buffer; var rle = new List(); progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + foreach (var batch in BatchLayersIndexes()) { foreach (var layerIndex in batch) @@ -796,33 +795,48 @@ public class OSFFile : FileFormat //Debug.WriteLine($"{layerIndex}: {inputFile.Position}"); layerDef[layerIndex] = Helpers.Deserialize(inputFile); + if (layerDef[layerIndex].NumberOfPixels == 0) continue; + int buffer; + int slen; while ((buffer = inputFile.ReadByte()) > -1) { - if (buffer != 0x0D) // Not what we want, add and continue + if ((buffer & 0x01) == 0x01) // It's a run + { + slen = inputFile.ReadByte(); + if (buffer == 0x0D && slen is 0x0A or 0x0B) + { + inputFile.Seek(-2, SeekOrigin.Current); + break; + } + + rle.Add((byte)buffer); + rle.Add((byte)slen); + + if ((slen & 0x80) == 0) + { + } + else if ((slen & 0xc0) == 0x80) + { + rle.Add((byte)inputFile.ReadByte()); + } + else if ((slen & 0xe0) == 0xc0) + { + rle.AddRange(inputFile.ReadBytes(2)); + } + else if ((slen & 0xf0) == 0xe0) + { + rle.AddRange(inputFile.ReadBytes(3)); + } + else + { + throw new FileLoadException("Corrupted RLE data"); + } + } + else { rle.Add((byte)buffer); - continue; } - - // Check next byte for 0x0A or 0x0B - buffer = inputFile.ReadByte(); - if (buffer is 0x0A or 0x0B) - { - inputFile.Seek(-2, SeekOrigin.Current); - break; - } - - rle.Add(0x0D); // Add previous byte - - if (buffer == -1) break; // End of file - if (buffer == 0x0D) // 0x0D pair, rewind 1 and recheck - { - inputFile.Seek(-1, SeekOrigin.Current); - continue; - } - - rle.Add((byte)buffer); } layerDef[layerIndex].EncodedRle = rle.ToArray(); diff --git a/UVtools.Core/Layers/MainIssue.cs b/UVtools.Core/Layers/MainIssue.cs index a6efa58..c33d789 100644 --- a/UVtools.Core/Layers/MainIssue.cs +++ b/UVtools.Core/Layers/MainIssue.cs @@ -123,7 +123,12 @@ public class MainIssue : IReadOnlyList Childs = new[] { issue }; issue.Parent = this; PixelCount = issue.PixelsCount; - if (issue is IssueOfPoints) Area = issue.PixelsCount; + Area = issue switch + { + IssueOfPoints => issue.PixelsCount, + IssueOfContours => issue.Area, + _ => issue.Area + }; } public MainIssue(IssueType type, IEnumerable issues) : this(type) @@ -131,7 +136,7 @@ public class MainIssue : IReadOnlyList foreach (var issue in issues) { issue.Parent = this; - var layerHeightInPixels = issue.Layer.SlicerFile.MillimetersToPixels(issue.Layer.LayerHeight, 20); + var layerHeightInPixels = issue.Layer.SlicerFile.MillimetersToPixelsF(issue.Layer.LayerHeight, 20); Area += issue.Area * layerHeightInPixels; PixelCount += issue.PixelsCount; if (issue.BoundingRectangle.IsEmpty) continue; @@ -154,7 +159,7 @@ public class MainIssue : IReadOnlyList Childs = issues.OrderBy(issue => issue.LayerIndex).ToArray(); } - Area = Math.Floor(Area); + Area = Math.Round(Area, 3); } private void Sort() diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 8456409..a6416f3 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.9.2 + 3.9.3 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 @@ -84,7 +84,7 @@ - + diff --git a/UVtools.Installer/Code/HeatGeneratedFileList.wxs b/UVtools.Installer/Code/HeatGeneratedFileList.wxs index fee2c02..376c652 100644 --- a/UVtools.Installer/Code/HeatGeneratedFileList.wxs +++ b/UVtools.Installer/Code/HeatGeneratedFileList.wxs @@ -320,8 +320,8 @@ - - + + @@ -1687,7 +1687,7 @@ - + diff --git a/UVtools.ScriptSample/ScriptBuildGrid.cs b/UVtools.ScriptSample/ScriptBuildGrid.cs new file mode 100644 index 0000000..c634903 --- /dev/null +++ b/UVtools.ScriptSample/ScriptBuildGrid.cs @@ -0,0 +1,119 @@ +using System; +using System.Drawing; +using UVtools.Core.Extensions; +using UVtools.Core.Scripting; +using Emgu.CV; +using Emgu.CV.CvEnum; + +namespace UVtools.ScriptSample; + +public class ScriptBuildGrid : ScriptGlobals +{ + readonly ScriptNumericalInput GridSpacing = new() + { + Label = "Size of the initial grains", + Unit = "px", + Minimum = 1, + Maximum = 10000, + Increment = 1, + Value = 200, + }; + + readonly ScriptNumericalInput GridWidth = new() + { + Label = "Width of line", + Unit = "px", + Minimum = 1, + Maximum = 500, + Increment = 1, + Value = 1, + }; + + readonly ScriptNumericalInput ExposureTime = new() + { + Label = "Exposure time", + Unit = "s", + Minimum = 0.1f, + Maximum = 1000f, + Increment = 0.5f, + DecimalPlates = 2, + Value = 1, + }; + + /// + /// Set configurations here, this function trigger just after load a script + /// + public void ScriptInit() + { + Script.Name = "Build grid pattern"; + Script.Description = "Useful for measuring backlight quality"; + Script.Author = "Jan Mrázek"; + Script.Version = new Version(0, 1); + Script.UserInputs.Add(GridSpacing); + Script.UserInputs.Add(GridWidth); + + Script.UserInputs.Add(ExposureTime); + ExposureTime.Value = SlicerFile.BottomExposureTime; + } + + /// + /// Validate user inputs here, this function trigger when user click on execute + /// + /// A error message, empty or null if validation passes. + public string? ScriptValidate() + { + return null; + } + + private Mat GenerateGridPattern() + { + var pattern = SlicerFile.CreateMat(); + + for (int x = pattern.Size.Width / 2; x < pattern.Size.Width; x += GridSpacing.Value) { + CvInvoke.Line(pattern, + new Point(x, 0), + new Point(x, pattern.Size.Height), + EmguExtensions.WhiteColor, GridWidth.Value, LineType.FourConnected); + CvInvoke.Line(pattern, + new Point(pattern.Size.Width - x, 0), + new Point(pattern.Size.Width - x, pattern.Size.Height), + EmguExtensions.WhiteColor, GridWidth.Value, LineType.FourConnected); + } + + for (int y = pattern.Size.Height / 2; y < pattern.Size.Height; y += GridSpacing.Value) { + CvInvoke.Line(pattern, + new Point(0, y), + new Point(pattern.Size.Width, y), + EmguExtensions.WhiteColor, GridWidth.Value, LineType.FourConnected); + CvInvoke.Line(pattern, + new Point(0, pattern.Size.Height - y), + new Point(pattern.Size.Width, pattern.Size.Height - y), + EmguExtensions.WhiteColor, GridWidth.Value, LineType.FourConnected); + } + + return pattern; + } + + /// + /// Execute the script, this function trigger when when user click on execute and validation passes + /// + /// True if executes successfully to the end, otherwise false. + public bool ScriptExecute() + { + Progress.Reset("Changing layers", 2); // Sets the progress name and number of items to process + + SlicerFile.Reallocate(2); + + using var pattern = GenerateGridPattern(); + SlicerFile[0].LayerMat = pattern; + Progress++; + SlicerFile[0].CopyImageTo(SlicerFile[1]); + Progress++; + + SlicerFile.BottomLayerCount = 2; // Sanitize bottom layer count + SlicerFile.BottomExposureTime = ExposureTime.Value; + SlicerFile.ExposureTime = ExposureTime.Value; + + return !Progress.Token.IsCancellationRequested; + } +} diff --git a/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs b/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs new file mode 100644 index 0000000..bab6cc4 --- /dev/null +++ b/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs @@ -0,0 +1,86 @@ +using System; +using System.Drawing; +using System.Threading.Tasks; +using UVtools.Core; +using UVtools.Core.Extensions; +using UVtools.Core.Scripting; +using Emgu.CV; + +namespace UVtools.ScriptSample; + +public class ScriptCompensateCrossBleeding : ScriptGlobals +{ + readonly ScriptNumericalInput LayerBleed = new() + { + Label = "Number of layers the exposure bleeds through", + Unit = "layers", + Minimum = 1, + Maximum = 500, + Increment = 1, + Value = 5, + }; + + public void ScriptInit() + { + Script.Name = "Mitigates effects of cross-layer bleeding"; + Script.Description = "Adjusts overhands so we can compensate for cross-layer curing"; + Script.Author = "Jan Mrázek"; + Script.Version = new Version(0, 1); + Script.UserInputs.Add(LayerBleed); + } + + public string? ScriptValidate() + { + return SlicerFile.LayerCount < 2 + ? "This script requires at least 2 layers in order to run." + : null; + } + + public bool ScriptExecute() + { + Progress.Reset("Changing layers", Operation.LayerRangeCount); // Sets the progress name and number of items to process + + var originalLayers = SlicerFile.CloneLayers(); + Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd + 1, + CoreSettings.GetParallelOptions(Progress), + layerIndex => + { + var layersBelowCount = layerIndex > LayerBleed.Value ? LayerBleed.Value : layerIndex; + + using var sourceMat = originalLayers[layerIndex].LayerMat; + var source = sourceMat.GetDataByteSpan(); + + using var targetMat = sourceMat.NewBlank(); + var target = targetMat.GetDataByteSpan(); + + using var occupancyMat = sourceMat.NewBlank(); + var occupancy = occupancyMat.GetDataByteSpan(); + + var sumRectangle = Rectangle.Empty; + for (int i = 0; i < layersBelowCount; i++) + { + using var mat = originalLayers[layerIndex - i - 1].LayerMat; + CvInvoke.Threshold(mat, mat, 1, 1, Emgu.CV.CvEnum.ThresholdType.Binary); + CvInvoke.Add(mat, occupancyMat, occupancyMat); + sumRectangle = sumRectangle.IsEmpty + ? originalLayers[layerIndex - i - 1].BoundingRectangle + : Rectangle.Union(sumRectangle, originalLayers[layerIndex - i - 1].BoundingRectangle); + } + + // Spare a few useless cycles depending on model volume on LCD + var optimizedStatingPixelIndex = sourceMat.GetPixelPos(sumRectangle.Location); + var optimizedEndingPixelIndex = sourceMat.GetPixelPos(sumRectangle.Right, sumRectangle.Bottom); + for (var i = optimizedStatingPixelIndex; i < optimizedEndingPixelIndex; i++) + { + if (layersBelowCount == 0 || occupancy[i] == layersBelowCount) + target[i] = source[i]; + } + + SlicerFile[layerIndex].LayerMat = targetMat; + Progress.LockAndIncrement(); + }); + + // return true if not cancelled by user + return !Progress.Token.IsCancellationRequested; + } +} diff --git a/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs b/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs new file mode 100644 index 0000000..ae9adc6 --- /dev/null +++ b/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Threading.Tasks; +using UVtools.Core; +using UVtools.Core.Extensions; +using UVtools.Core.Scripting; +using UVtools.Core.Layers; +using Emgu.CV; +using Emgu.CV.CvEnum; + +namespace UVtools.ScriptSample; + +public class ScriptPreventResinShrinkage : ScriptGlobals +{ + readonly ScriptNumericalInput GrainSize = new() + { + Label = "Size of the initial grains", + Unit = "px", + Minimum = 1, + Maximum = 500, + Increment = 1, + Value = 11, + }; + + readonly ScriptNumericalInput Spacing = new() + { + Label = "Free space between the grains", + Unit = "px", + Minimum = 1, + Maximum = 500, + Increment = 1, + Value = 9, + }; + + /// + /// Set configurations here, this function trigger just after load a script + /// + public void ScriptInit() + { + Script.Name = "Preventing the effects of resin shrinkage"; + Script.Description = "Cures a layer in multiple exposures to mitigate resin shrinkage effects"; + Script.Author = "Jan Mrázek"; + Script.Version = new Version(0, 2); + Script.UserInputs.Add(GrainSize); + Script.UserInputs.Add(Spacing); + } + + /// + /// Validate user inputs here, this function trigger when user click on execute + /// + /// A error message, empty or null if validation passes. + public string? ScriptValidate() + { + return SlicerFile.CanUseLayerPositionZ ? null : "Your printer/file format is not supported: Unable to have multiple layers in same Z position."; + } + + private Mat GenerateDotPattern() { + var pattern = SlicerFile.CreateMat(); + + var xStep = GrainSize.Value + Spacing.Value; + var yStep = (GrainSize.Value + Spacing.Value) / 2; + var evenRow = false; + for (int y = 0; y < pattern.Size.Height; y += yStep) { + for (int x = 0; x < pattern.Size.Width; x += xStep) { + CvInvoke.Circle(pattern, + new Point(x + (evenRow ? xStep / 2 : 0), y), + GrainSize.Value / 2, + EmguExtensions.WhiteColor, + -1, LineType.FourConnected); + } + evenRow = !evenRow; + } + + return pattern; + } + + private Mat GenerateLinePattern() { + var pattern = SlicerFile.CreateMat(); + + var width = GrainSize.Value / 5; + if (width == 0) + width = 1; + var step = GrainSize.Value + Spacing.Value; + for (int x = 0; x < pattern.Size.Width; x += step) { + CvInvoke.Line(pattern, + new Point(x, 0), + new Point(x + pattern.Size.Height, pattern.Size.Height), + EmguExtensions.WhiteColor, width, LineType.FourConnected); + CvInvoke.Line(pattern, + new Point(x, pattern.Size.Height), + new Point(x + pattern.Size.Height, 0), + EmguExtensions.WhiteColor, width, LineType.FourConnected); + } + for (int y = 0; y < pattern.Size.Height; y += step) { + CvInvoke.Line(pattern, + new Point(0, y), + new Point(pattern.Size.Height, y + pattern.Size.Height), + EmguExtensions.WhiteColor, width, LineType.FourConnected); + CvInvoke.Line(pattern, + new Point(0, y), + new Point(pattern.Size.Height, y - pattern.Size.Height), + EmguExtensions.WhiteColor, width, LineType.FourConnected); + } + return pattern; + } + + /// + /// Execute the script, this function trigger when when user click on execute and validation passes + /// + /// True if executes successfully to the end, otherwise false. + public bool ScriptExecute() + { + Progress.Reset("Changing layers", Operation.LayerRangeCount); // Sets the progress name and number of items to process + + var newLayers = new List((int)SlicerFile.LayerCount * 3); + var dotPattern = GenerateDotPattern(); + var linePattern = GenerateLinePattern(); + + using var inverseDotPattern = new Mat(); + using var dotLinePattern = new Mat(); + + CvInvoke.BitwiseNot(dotPattern, inverseDotPattern); + CvInvoke.Dilate(inverseDotPattern, inverseDotPattern, EmguExtensions.Kernel3x3Rectangle, + new Point(-1, -1), 1, BorderType.Reflect101, default); + + CvInvoke.BitwiseAnd(inverseDotPattern, linePattern, dotLinePattern); + + Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd + 1, + CoreSettings.GetParallelOptions(Progress), + layerIndex => + { + var fullLayer = SlicerFile[layerIndex]; + if (fullLayer.IsEmpty) + { + newLayers.Add(fullLayer); + return; // Do not apply to empty layers + } + + var coresLayer1 = fullLayer.Clone(); + var coresLayer2 = fullLayer.Clone(); + + using var coresMat1 = fullLayer.LayerMat; + + // Ensure there is something we can attach to in the previous layer + if (layerIndex > 0) { + using var previousLayerMat = SlicerFile[layerIndex - 1].LayerMat; + CvInvoke.BitwiseAnd(coresMat1, previousLayerMat, coresMat1); + } + using var coresMat2 = coresMat1.Clone(); + + CvInvoke.BitwiseAnd(coresMat1, dotPattern, coresMat1); + CvInvoke.BitwiseAnd(coresMat2, dotLinePattern, coresMat2); + + + coresLayer1.LayerMat = coresMat1; + coresLayer2.LayerMat = coresMat2; + + // Try to disable lifts for last two subsequent layers + fullLayer.LiftHeightTotal = coresLayer2.LiftHeightTotal = SlicerFile.SupportsGCode ? 0f : 0.1f; + + newLayers.Add(coresLayer1); + newLayers.Add(coresLayer2); + newLayers.Add(fullLayer); + + Progress.LockAndIncrement(); + }); + + SlicerFile.SuppressRebuildPropertiesWork(() => { + SlicerFile.Layers = newLayers.ToArray(); + }); + // return true if not cancelled by user + return !Progress.Token.IsCancellationRequested; + } +} diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 3ce41f3..5451fa7 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ LICENSE https://github.com/sn4k3/UVtools Git - 3.9.2 + 3.9.3 AnyCPU;x64 UVtools.png README.md @@ -44,9 +44,9 @@ - - - + + + diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml index 0a9dcd2..d1ff60c 100644 --- a/documentation/UVtools.Core.xml +++ b/documentation/UVtools.Core.xml @@ -3502,6 +3502,14 @@ Fallback to this value in pixels if no ratio is available to make the convertion Pixels + + + Converts millimeters to pixels given the current resolution and display size + + Millimeters to convert + Fallback to this value in pixels if no ratio is available to make the convertion + Pixels + From a pixel position get the equivalent position on the display