diff --git a/CHANGELOG.md b/CHANGELOG.md index d4efb8f1..26e62d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 03/12/2022 - v3.9.1 + +- (Improvement) Performance on pixel editor when using high count of layers below and/or above +- (Fix) Unable to disable specific issues detection + ## 01/12/2022 - v3.9.0 - **File formats:** diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 92cc23a7..89d2cea0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,33 +1,3 @@ -- **File formats:** - - (Add) File extension: .gktwo.ctb to `ChituboxFile` to be able to convert files for UniFormation GKtwo under special CTB format - - (Add) UniFormation GKtwo compatibility under CTB format, if exporting JXS rename to CTB before open - - (Fix) CTB, CBDDLP, PHOTON, FDG, PHZ: Read and write files larger then 4GB (#608) -- **PCB Exposure:** - - (Add) Offset X/Y to offset the PCB from it origin - - (Add) Allow to toggle between "Show preview image cropped by it bounds" and "Show full preview image (The final result)" - - (Improvement) Use rectangle instead of line for center line primitive (#607) - - (Fix) Implement rotation to polygon and center line primitives (#607) - - (Fix) Macros in a single line was not being parsed (#607) - - (Fix) Invert color per file was not affecting primitives -- **Network printers:** - - (Add) Socket requests with TCP and UDP - - (Add) AnyCubic printer preset (However it can't upload a file) - - (Add) Scripts in request path to allow a first request to fetch data to the final request: - - A script starts with **<\?** and ends with **?>** - - First parameter is the first request to get response content from - - Second parameter is the regex pattern to match content with - - Third parameter is the final request that supports a parameter from regex matching group, eg: **{#1}** is match Group[1] value - - **Example:** <\? getfiles > {0}\/(\d+\.[\da-zA-Z]+), > printfile,{#1} ?> - - (Change) Allow to print a filename without send it when upload request path is empty - - (Fix) Do not show printers with empty requests -- (Change) Default layer compression to Lz4 instead of Png -- (Improvement) Application is now culture aware but set part of `NumberFormat` to the `InvariantCulture.NumberFormat` -- (Improvement) Material cost now show with the current culture currency symbol due previous change -- (Improvement) Better submit of bug reports using sections and forms -- (Improvement) Linux: AppImage now have a help manual with possible arguments and parameters -- (Improvement) macOS: Codesign app on auto-installer and auto-upgrade to bypass arm64 run restriction (#431) -- (Improvement) macOS: Rebuilt arm64 libcvextern.dylib to run with less dependencies (#431) -- (Improvement) macOS: Try to show missing dependencies from openCV (if any) on the error message -- (Fix) UI: layers sorted lexicographically instead of numerically in the issues list view (#611) -- (Fix) PrusaSlicer printer parameters: UniFormation GKtwo +- (Improvement) Performance on pixel editor when using high count of layers below and/or above +- (Fix) Unable to disable specific issues detection diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 0b74d6c4..effe3e1b 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -5729,6 +5729,18 @@ public bool TryParseLayerIndexRange(string value, out uint layerIndexStart, out return layerIndexStart <= layerIndexEnd; } + /// + /// Constrains a layer index to be inside the range between 0 and + /// + /// Layer index to sanitize + /// True if sanitized, otherwise false + public bool SanitizeLayerIndex(ref int layerIndex) + { + var originalValue = layerIndex; + layerIndex = Math.Clamp(layerIndex, 0, (int)LastLayerIndex); + return originalValue != layerIndex; + } + /// /// Constrains a layer index to be inside the range between 0 and /// @@ -5741,11 +5753,25 @@ public bool SanitizeLayerIndex(ref uint layerIndex) return originalValue != layerIndex; } + /// + /// Constrains a layer index to be inside the range between 0 and + /// + /// Layer index to sanitize + public uint SanitizeLayerIndex(int layerIndex) + { + return (uint)Math.Clamp(layerIndex, 0, LastLayerIndex); + } + + /// + /// Constrains a layer index to be inside the range between 0 and + /// + /// Layer index to sanitize public uint SanitizeLayerIndex(uint layerIndex) { return Math.Min(layerIndex, LastLayerIndex); } + /// /// Re-assign layer indexes and parent /// diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index d545cf4b..7daab2c1 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -217,7 +217,7 @@ public virtual uint LayerIndexStart { if (SlicerFile is not null) { - value = Math.Min(value, SlicerFile.LastLayerIndex); + SlicerFile.SanitizeLayerIndex(ref value); } if (!RaiseAndSetIfChanged(ref _layerIndexStart, value)) return; RaisePropertyChanged(nameof(LayerRangeCount)); @@ -235,7 +235,7 @@ public virtual uint LayerIndexEnd { if (SlicerFile is not null) { - value = Math.Min(value, SlicerFile.LastLayerIndex); + SlicerFile.SanitizeLayerIndex(ref value); } if(!RaiseAndSetIfChanged(ref _layerIndexEnd, value)) return; RaisePropertyChanged(nameof(LayerRangeCount)); @@ -463,7 +463,7 @@ public void SelectLastLayer() public void SelectFirstToCurrentLayer(uint currentLayerIndex) { LayerIndexStart = 0; - LayerIndexEnd = Math.Min(currentLayerIndex, SlicerFile.LastLayerIndex); + LayerIndexEnd = SlicerFile.SanitizeLayerIndex(currentLayerIndex); } /// @@ -472,7 +472,7 @@ public void SelectFirstToCurrentLayer(uint currentLayerIndex) /// From layer index to select public void SelectCurrentToLastLayer(uint currentLayerIndex) { - LayerIndexStart = Math.Min(currentLayerIndex, SlicerFile.LastLayerIndex); + LayerIndexStart = SlicerFile.SanitizeLayerIndex(currentLayerIndex); LayerIndexEnd = SlicerFile.LastLayerIndex; } diff --git a/UVtools.Core/Operations/OperationLayerArithmetic.cs b/UVtools.Core/Operations/OperationLayerArithmetic.cs index 748194bf..925d784c 100644 --- a/UVtools.Core/Operations/OperationLayerArithmetic.cs +++ b/UVtools.Core/Operations/OperationLayerArithmetic.cs @@ -163,7 +163,7 @@ public bool Parse() { uint.TryParse(rangeSplit[0], out var startLayer); if (!uint.TryParse(rangeSplit[1], out var endLayer)) endLayer = SlicerFile.LastLayerIndex; - endLayer = Math.Min(endLayer, SlicerFile.LastLayerIndex); + SlicerFile.SanitizeLayerIndex(ref endLayer); for (var index = startLayer; index <= endLayer; index++) { if (group.SetLayers.Contains(index)) continue; diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 1d48b58f..eca31b9f 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.0 + 3.9.1 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 2a1273c1..c1823f72 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -232,7 +232,7 @@ public async void RemoveRepairIssues(IEnumerable issues, bool promptC Clipboard.Snapshot(); - var task = await Task.Factory.StartNew(() => + var task = await Task.Run(() => { Progress.Reset("Removing selected issues", (uint)processParallelIssues.Count); try @@ -448,7 +448,7 @@ private async Task UpdateIslandsOverhangs(List whiteListLayers) }*/ - var resultIssues = await Task.Factory.StartNew(() => + var resultIssues = await Task.Run(() => { try { @@ -636,7 +636,7 @@ private async Task ComputeIssues(IssuesDetectionConfiguration config) IsGUIEnabled = false; ShowProgressWindow("Computing Issues"); - var resultIssues = await Task.Factory.StartNew(() => + var resultIssues = await Task.Run(() => { try { @@ -825,7 +825,19 @@ public void SetResinTrapDetectionStartLayer(char which) } } - public IssuesDetectionConfiguration GetIssuesDetectionConfiguration(bool enable = true) + public IssuesDetectionConfiguration GetIssuesDetectionConfiguration() + { + return new IssuesDetectionConfiguration( + GetIslandDetectionConfiguration(), + GetOverhangDetectionConfiguration(), + GetResinTrapDetectionConfiguration(), + GetTouchingBoundsDetectionConfiguration(), + GetPrintHeightDetectionConfiguration(), + GetEmptyLayerDetectionConfiguration() + ); + } + + public IssuesDetectionConfiguration GetIssuesDetectionConfiguration(bool enable) { return new IssuesDetectionConfiguration( GetIslandDetectionConfiguration(enable), diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs index 91fe0240..33a3fc93 100644 --- a/UVtools.WPF/MainWindow.LayerPreview.cs +++ b/UVtools.WPF/MainWindow.LayerPreview.cs @@ -813,13 +813,13 @@ public void GoLastLayer() public void GoUpLayers(uint layers) { if (!IsFileLoaded) return; - ActualLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + layers); + ActualLayer = SlicerFile.SanitizeLayerIndex(ActualLayer + layers); } public void GoDownLayers(uint layers) { if (!IsFileLoaded) return; - ActualLayer = (uint)Math.Max(0, (int)ActualLayer - layers); + ActualLayer = SlicerFile.SanitizeLayerIndex((int)ActualLayer - (int)layers); } public void GoMassLayer(string which) diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs index c46e6e1f..2c1a3246 100644 --- a/UVtools.WPF/MainWindow.PixelEditor.cs +++ b/UVtools.WPF/MainWindow.PixelEditor.cs @@ -174,19 +174,21 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) WriteableBitmap bitmap = (WriteableBitmap)LayerImageBox.Image; //var context = CreateRenderTarget().CreateDrawingContext(bitmap); - + //Bitmap bmp = pbLayer.Image as Bitmap; if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Drawing) { - uint minLayer = (uint) Math.Max(0, (int)_actualLayer - DrawingPixelDrawing.LayersBelow); - uint maxLayer = Math.Min(SlicerFile.LastLayerIndex, _actualLayer + DrawingPixelDrawing.LayersAbove); + var drawings = new List(); + uint minLayer = SlicerFile.SanitizeLayerIndex((int)ActualLayer - (int)DrawingPixelDrawing.LayersBelow); + uint maxLayer = SlicerFile.SanitizeLayerIndex(ActualLayer + DrawingPixelDrawing.LayersAbove); for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++) { var operationDrawing = new PixelDrawing(layerIndex, realLocation, DrawingPixelDrawing.LineType, DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.RotationAngle, DrawingPixelDrawing.BrushSize, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.RemovePixelBrightness, DrawingPixelDrawing.PixelBrightness, isAdd); //if (PixelHistory.Contains(operation)) continue; - AddDrawing(operationDrawing); + //AddDrawing(operationDrawing); + drawings.Add(operationDrawing); if (layerIndex == _actualLayer) { @@ -386,13 +388,18 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) //RefreshLayerImage(); } } + + AddDrawings(drawings); + return; } else if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Text) { if (string.IsNullOrEmpty(DrawingPixelText.Text) || DrawingPixelText.FontScale < 0.2) return; - uint minLayer = (uint) Math.Max(0, (int)ActualLayer - DrawingPixelText.LayersBelow); - uint maxLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + DrawingPixelText.LayersAbove); + var drawings = new List(); + uint minLayer = SlicerFile.SanitizeLayerIndex((int)ActualLayer - (int)DrawingPixelText.LayersBelow); + uint maxLayer = SlicerFile.SanitizeLayerIndex(ActualLayer + DrawingPixelText.LayersAbove); + for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++) { var operationText = new PixelText(layerIndex, realLocation, DrawingPixelText.LineType, @@ -401,8 +408,9 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) //if (PixelHistory.Contains(operation)) continue; //PixelHistory.Add(operation); - AddDrawing(operationText); - + //AddDrawing(operationText); + drawings.Add(operationText); + /*var color = isAdd ? Settings.PixelEditor.AddPixelColor : Settings.PixelEditor.RemovePixelColor; @@ -415,20 +423,24 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) }*/ } + AddDrawings(drawings); ShowLayer(); return; } else if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Eraser) { if (LayerCache.Image.GetByte(realLocation) < 10) return; - uint minLayer = (uint) Math.Max(0, (int)ActualLayer - DrawingPixelEraser.LayersBelow); - uint maxLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + DrawingPixelEraser.LayersAbove); + + var drawings = new List(); + uint minLayer = SlicerFile.SanitizeLayerIndex((int)ActualLayer - (int)DrawingPixelEraser.LayersBelow); + uint maxLayer = SlicerFile.SanitizeLayerIndex(ActualLayer + DrawingPixelEraser.LayersAbove); for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++) { var operationEraser = new PixelEraser(layerIndex, realLocation, DrawingPixelEraser.PixelBrightness); //if (PixelHistory.Contains(operation)) continue; - AddDrawing(operationEraser); + //AddDrawing(operationEraser); + drawings.Add(operationEraser); /*if (layerIndex == _actualLayer) { @@ -445,6 +457,7 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) }*/ } + AddDrawings(drawings); ShowLayer(); return; } @@ -461,6 +474,7 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) CvInvoke.Circle(LayerCache.ImageBgr, location, operationSupport.TipDiameter / 2, new MCvScalar(Settings.PixelEditor.SupportsColor.B, Settings.PixelEditor.SupportsColor.G, Settings.PixelEditor.SupportsColor.R), -1); RefreshLayerImage(); + return; } else if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.DrainHole) { @@ -473,6 +487,7 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) CvInvoke.Circle(LayerCache.ImageBgr, location, operationDrainHole.Diameter / 2, new MCvScalar(Settings.PixelEditor.DrainHoleColor.B, Settings.PixelEditor.DrainHoleColor.G, Settings.PixelEditor.DrainHoleColor.R), -1); RefreshLayerImage(); + return; } else { @@ -482,11 +497,18 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) public void AddDrawing(PixelOperation operation) { + operation.Index = (uint)Drawings.Count; Drawings.Insert(0, operation); - for (int i = 0; i < Drawings.Count; i++) + } + + public void AddDrawings(IEnumerable operations) + { + uint count = (uint)Drawings.Count; + foreach (var operation in operations) { - Drawings[i].Index = (uint) (Drawings.Count - i); + operation.Index = count++; } + Drawings.InsertRange(0, operations); } public async void DrawModifications(bool exitEditor) @@ -534,12 +556,12 @@ public async void DrawModifications(bool exitEditor) ShowProgressWindow("Drawing pixels"); Clipboard.Snapshot(); - var task = await Task.Factory.StartNew(async () => + var task = await Task.Run(() => { try { SlicerFile.DrawModifications(Drawings, Progress); - return true; + return Task.FromResult(true); } catch (OperationCanceledException) { @@ -547,18 +569,18 @@ public async void DrawModifications(bool exitEditor) } catch (Exception ex) { - await Dispatcher.UIThread.InvokeAsync(async () => + Dispatcher.UIThread.InvokeAsync(async () => { await this.MessageBoxError(ex.ToString(), "Drawing operation failed!"); }); } - return false; + return Task.FromResult(false); }); IsGUIEnabled = true; - if (!task.Result) + if (!task) { Clipboard.RestoreSnapshot(); ShowLayer(); diff --git a/UVtools.WPF/MainWindow.Suggestions.cs b/UVtools.WPF/MainWindow.Suggestions.cs index 62a85a6e..84b0605e 100644 --- a/UVtools.WPF/MainWindow.Suggestions.cs +++ b/UVtools.WPF/MainWindow.Suggestions.cs @@ -93,7 +93,7 @@ public async void ApplySuggestionsClicked() IsGUIEnabled = false; ShowProgressWindow($"Applying {suggestions.Length} suggestions", false); - var executed = await Task.Factory.StartNew(() => + var executed = await Task.Run(() => { uint executed = 0; @@ -141,7 +141,7 @@ public async void ApplySuggestionClicked(Suggestion suggestion) IsGUIEnabled = false; ShowProgressWindow(suggestion.Title, false); - var result = await Task.Factory.StartNew(() => + var result = await Task.Run(() => { try { diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 8f98c8ac..a05513b9 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -201,6 +201,7 @@ public TabItem SelectedTabItem if (ReferenceEquals(_selectedTabItem, TabIssues) && Settings.Issues.ComputeIssuesOnClickTab) { Dispatcher.UIThread.InvokeAsync(async () => await OnClickDetectIssues()); + } } } @@ -641,7 +642,7 @@ await this.MessageBoxError($"The device {drive.Name} is not ready/available at t { Progress.ResetAll($"Ejecting {removableDrive.Name}"); - var ejectResult = await Task.Factory.StartNew(() => + var ejectResult = await Task.Run(() => { try { @@ -704,7 +705,7 @@ protected override void OnOpened(EventArgs e) if (Settings.General.CheckForUpdatesOnStartup) { - Task.Factory.StartNew(() => VersionChecker.Check()); + Task.Run(() => VersionChecker.Check()); } ProcessFiles(Program.Args); @@ -1047,7 +1048,7 @@ public async void MenuFileSettingsClicked() IsGUIEnabled = false; ShowProgressWindow($"Changing layers compression codec from {oldLayerCompressionCodec.ToString().ToUpper()} to {Settings.General.LayerCompressionCodec.ToString().ToUpper()}"); - await Task.Factory.StartNew(() => + await Task.Run(() => { try { @@ -1335,7 +1336,7 @@ async void ProcessFile(string fileName, FileFormat.FileDecodeType fileDecodeType await this.MessageBoxError(exception.ToString(), "Error opening the file"); }*/ - var task = await Task.Factory.StartNew( () => + var task = await Task.Run( () => { try { @@ -1461,7 +1462,7 @@ await this.MessageBoxError("It seems this file has no layers. Possible causes c ShowProgressWindow( $"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {convertFileExtension}"); - task = await Task.Factory.StartNew(() => + task = await Task.Run(() => { try { @@ -1831,7 +1832,7 @@ private async void ConvertToOnTapped(object? sender, RoutedEventArgs e) var oldFile = SlicerFile.FileFullPath!; ShowProgressWindow($"Converting {oldFileName} to {Path.GetExtension(newFilePath)}"); - var task = await Task.Factory.StartNew(() => + var task = await Task.Run(() => { try { @@ -1926,7 +1927,7 @@ public async Task SaveFile(string filepath = null, bool ignoreOverwriteWar var oldFile = SlicerFile.FileFullPath; - var task = await Task.Factory.StartNew( () => + var task = await Task.Run( () => { try { @@ -2114,7 +2115,7 @@ public async Task RunOperation(Operation baseOperation) if (config.IslandConfig.Enabled || config.ResinTrapConfig.Enabled) { - await ComputeIssues(config); + ComputeIssues(config); } } @@ -2127,7 +2128,7 @@ public async Task RunOperation(Operation baseOperation) Clipboard.Snapshot(); - var result = await Task.Factory.StartNew(() => + var result = await Task.Run(() => { try { diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index a669366b..0b39ead6 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.0 + 3.9.1 AnyCPU;x64 UVtools.png README.md diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs index 1df71e98..ce6cfbc2 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml.cs +++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs @@ -118,7 +118,7 @@ public uint LayerIndexStart get => _layerIndexStart; set { - value = Math.Min(value, SlicerFile.LastLayerIndex); + SlicerFile.SanitizeLayerIndex(ref value); if (ToolControl?.BaseOperation is not null) { @@ -146,7 +146,7 @@ public uint LayerIndexEnd get => _layerIndexEnd; set { - value = Math.Min(value, SlicerFile.LastLayerIndex); + SlicerFile.SanitizeLayerIndex(ref value); if (ToolControl?.BaseOperation is not null) { diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml index 85bbf504..2dcc83c0 100644 --- a/documentation/UVtools.Core.xml +++ b/documentation/UVtools.Core.xml @@ -3667,6 +3667,13 @@ Parsed ending layer index + + + Constrains a layer index to be inside the range between 0 and + + Layer index to sanitize + True if sanitized, otherwise false + Constrains a layer index to be inside the range between 0 and @@ -3674,6 +3681,18 @@ Layer index to sanitize True if sanitized, otherwise false + + + Constrains a layer index to be inside the range between 0 and + + Layer index to sanitize + + + + Constrains a layer index to be inside the range between 0 and + + Layer index to sanitize + Re-assign layer indexes and parent