From 6d6be66ced23339f8d30f4aed2d32b83d14384af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Fri, 30 Apr 2021 18:20:58 +0100 Subject: v2.9.2 - (Upgrade) AvaloniaUI from 0.10 to 0.10.2 - (Remove) Unused assemblies - **Issues** - Improve the performance when loading big lists of issues into the DataGrid - Auto refresh issues on the vertical highlight tracker once cath a modification on the Issues list - **Layer preview - Difference:** - Layer difference will now only check the pixels inside the union of previous, current and next layer bounding rectangle, increasing the performance and speed - Previous and next layer pixels if both exists was not showing with the configured color and using the next layer color instead - Respect Anti-Aliasing pixels and fade colors accordingly - Unlock the possiblity of using the layer difference on first and last layer - Add a option to show similar pixels instead of the difference - Change previous default color from (255, 0, 255) to (81, 131, 82) for better depth preception - Change next default color from (0, 255, 255) to (81, 249, 252) for better depth preception - Change previous & next default color from (255, 0, 0) to (246, 240, 216) for better depth preception - **(Fix) Pixel editor:** - Modification was append instead of prepend on the list - Modification was not updating the index number on the list - (Fix) PrusaSlicer printer: Bene4 Mono screen, bed and height size --- CHANGELOG.md | 22 + PrusaSlicer/printer/Nova3D Bene4 Mono.ini | 10 +- UVtools.Core/Extensions/PathExtensions.cs | 8 +- UVtools.Core/Extensions/StreamExtensions.cs | 8 +- UVtools.Core/Extensions/StringExtensions.cs | 4 +- UVtools.Core/Extensions/ZipArchiveExtensions.cs | 144 ++--- UVtools.Core/FileFormats/SL1File.cs | 4 +- UVtools.Core/Managers/ClipboardManager.cs | 2 +- UVtools.Core/Managers/MaterialManager.cs | 17 +- UVtools.Core/Objects/RangeObservableCollection.cs | 703 +++++++++++++++++++++ .../Operations/OperationCalibrateExposureFinder.cs | 13 +- .../Operations/OperationDynamicLayerHeight.cs | 10 +- UVtools.Core/Operations/OperationLayerImport.cs | 45 +- UVtools.Core/UVtools.Core.csproj | 6 +- UVtools.InstallerMM/UVtools.InstallerMM.wxs | 15 - UVtools.WPF/App.axaml.cs | 3 +- UVtools.WPF/Assets/Styles/Styles.xaml | 11 - .../CalibrateExposureFinderControl.axaml.cs | 4 +- UVtools.WPF/Controls/KernelControl.axaml.cs | 4 +- .../Controls/Tools/ToolLayerImportControl.axaml.cs | 7 +- UVtools.WPF/MainWindow.Information.cs | 6 +- UVtools.WPF/MainWindow.Issues.cs | 32 +- UVtools.WPF/MainWindow.LayerPreview.cs | 107 +++- UVtools.WPF/MainWindow.Log.cs | 2 +- UVtools.WPF/MainWindow.PixelEditor.cs | 50 +- UVtools.WPF/MainWindow.axaml | 17 +- UVtools.WPF/MainWindow.axaml.cs | 2 +- UVtools.WPF/Structures/PEProfileFolder.cs | 4 +- UVtools.WPF/Structures/PixelPicker.cs | 1 - UVtools.WPF/Structures/SlicerProperty.cs | 15 +- UVtools.WPF/UVtools.WPF.csproj | 15 +- UVtools.WPF/UserSettings.cs | 39 +- UVtools.WPF/Windows/AboutWindow.axaml | 2 +- UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs | 3 +- UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml | 5 +- UVtools.WPF/Windows/SettingsWindow.axaml | 11 +- UVtools.WPF/Windows/ToolWindow.axaml | 3 +- UVtools.WPF/Windows/ToolWindow.axaml.cs | 6 +- 38 files changed, 1056 insertions(+), 304 deletions(-) create mode 100644 UVtools.Core/Objects/RangeObservableCollection.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 063050d..4d77e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 30/04/2021 - v2.9.2 + +- (Upgrade) AvaloniaUI from 0.10 to 0.10.2 +- (Remove) Unused assemblies +- **Issues** + - Improve the performance when loading big lists of issues into the DataGrid + - Auto refresh issues on the vertical highlight tracker once cath a modification on the Issues list +- **Layer preview - Difference:** + - Layer difference will now only check the pixels inside the union of previous, current and next layer bounding rectangle, increasing the performance and speed + - Previous and next layer pixels if both exists was not showing with the configured color and using the next layer color instead + - Respect Anti-Aliasing pixels and fade colors accordingly + - Unlock the possiblity of using the layer difference on first and last layer + - Add a option to show similar pixels instead of the difference + - Change previous default color from (255, 0, 255) to (81, 131, 82) for better depth preception + - Change next default color from (0, 255, 255) to (81, 249, 252) for better depth preception + - Change previous & next default color from (255, 0, 0) to (246, 240, 216) for better depth preception +- **(Fix) Pixel editor:** + - Modification was append instead of prepend on the list + - Modification was not updating the index number on the list +- (Fix) PrusaSlicer printer: Bene4 Mono screen, bed and height size + ## 18/04/2021 - v2.9.1 * **File formats:** @@ -12,6 +33,7 @@ * **GUI:** * (Change) Progress window to be a grid element inside MainWindow, this allow to reuse the graphics and its elements without the need of spawning a Window instance everytime a progress is shown, resulting in better performance and more fluid transaction * (Improvement) Clear issues when generating calibration tests + * (Fix) In some cases the process remains alive after quit the program ## 14/04/2021 - v2.9.0 diff --git a/PrusaSlicer/printer/Nova3D Bene4 Mono.ini b/PrusaSlicer/printer/Nova3D Bene4 Mono.ini index 21b2736..9c10514 100644 --- a/PrusaSlicer/printer/Nova3D Bene4 Mono.ini +++ b/PrusaSlicer/printer/Nova3D Bene4 Mono.ini @@ -1,18 +1,18 @@ -# generated by PrusaSlicer 2.3.0+win64 on 2021-01-13 at 02:35:43 UTC +# generated by PrusaSlicer 2.3.1+win64 on 2021-04-29 at 17:56:19 UTC absolute_correction = 0 area_fill = 50 bed_custom_model = bed_custom_texture = -bed_shape = 0x0,65.02x0,65.02x116,0x116 +bed_shape = 0x0,79.86x0,79.86x129.998,0x129.998 default_sla_material_profile = Prusa Orange Tough 0.05 default_sla_print_profile = 0.05 Normal -display_height = 116 +display_height = 129.998 display_mirror_x = 0 display_mirror_y = 0 display_orientation = landscape display_pixels_x = 1566 display_pixels_y = 2549 -display_width = 65.02 +display_width = 79.865 elefant_foot_compensation = 0.2 elefant_foot_min_width = 0.2 fast_tilt_time = 5 @@ -21,7 +21,7 @@ host_type = octoprint inherits = Original Prusa SL1 max_exposure_time = 120 max_initial_exposure_time = 300 -max_print_height = 130 +max_print_height = 150 min_exposure_time = 1 min_initial_exposure_time = 1 print_host = diff --git a/UVtools.Core/Extensions/PathExtensions.cs b/UVtools.Core/Extensions/PathExtensions.cs index e40290b..1b3be72 100644 --- a/UVtools.Core/Extensions/PathExtensions.cs +++ b/UVtools.Core/Extensions/PathExtensions.cs @@ -29,11 +29,9 @@ namespace UVtools.Core.Extensions foreach (var extension in extensions) { var dotExtension = $".{extension}"; - if (path.EndsWith(dotExtension)) - { - strippedExtension = extension; - return path.Remove(path.Length - dotExtension.Length); - } + if (!path.EndsWith(dotExtension)) continue; + strippedExtension = extension; + return path.Remove(path.Length - dotExtension.Length); } return path; diff --git a/UVtools.Core/Extensions/StreamExtensions.cs b/UVtools.Core/Extensions/StreamExtensions.cs index f85f917..c1404e0 100644 --- a/UVtools.Core/Extensions/StreamExtensions.cs +++ b/UVtools.Core/Extensions/StreamExtensions.cs @@ -19,11 +19,9 @@ namespace UVtools.Core.Extensions /// Byte array data public static byte[] ToArray(this Stream stream) { - using (var memoryStream = new MemoryStream()) - { - stream.CopyTo(memoryStream); - return memoryStream.ToArray(); - } + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + return memoryStream.ToArray(); } } } diff --git a/UVtools.Core/Extensions/StringExtensions.cs b/UVtools.Core/Extensions/StringExtensions.cs index 5f4a77b..9967c66 100644 --- a/UVtools.Core/Extensions/StringExtensions.cs +++ b/UVtools.Core/Extensions/StringExtensions.cs @@ -31,7 +31,7 @@ namespace UVtools.Core.Extensions { case null: throw new ArgumentNullException(nameof(input)); case "": throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)); - default: return input.First().ToString().ToUpper() + input.Substring(1); + default: return input.First().ToString().ToUpper() + input[1..]; } } @@ -47,7 +47,7 @@ namespace UVtools.Core.Extensions public static T Convert(this string input) { var converter = TypeDescriptor.GetConverter(typeof(T)); - if (converter != null) + if (converter is not null) { //Cast ConvertFromString(string text) : object to (T) return (T)converter.ConvertFromString(input); diff --git a/UVtools.Core/Extensions/ZipArchiveExtensions.cs b/UVtools.Core/Extensions/ZipArchiveExtensions.cs index 6be91bc..5d8924a 100644 --- a/UVtools.Core/Extensions/ZipArchiveExtensions.cs +++ b/UVtools.Core/Extensions/ZipArchiveExtensions.cs @@ -58,10 +58,8 @@ namespace UVtools.Core.Extensions public static void ImprovedExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer) { //Opens the zip file up to be read - using (ZipArchive archive = ZipFile.OpenRead(sourceArchiveFileName)) - { - archive.ImprovedExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, overwriteMethod); - } + using var archive = ZipFile.OpenRead(sourceArchiveFileName); + archive.ImprovedExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, overwriteMethod); } /// @@ -82,7 +80,7 @@ namespace UVtools.Core.Extensions public static void ImprovedExtractToDirectory(this ZipArchive archive, string sourceArchiveFileName, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer) { //Loops through each file in the zip file - foreach (ZipArchiveEntry file in archive.Entries) + foreach (var file in archive.Entries) { file.ImprovedExtractToFile(destinationDirectoryName, overwriteMethod); } @@ -207,73 +205,71 @@ namespace UVtools.Core.Extensions } //Opens the zip file in the mode we specified - using (ZipArchive zipFile = ZipFile.Open(archiveFullName, mode)) + using ZipArchive zipFile = ZipFile.Open(archiveFullName, mode); + //This is a bit of a hack and should be refactored - I am + //doing a similar foreach loop for both modes, but for Create + //I am doing very little work while Update gets a lot of + //code. This also does not handle any other mode (of + //which there currently wouldn't be one since we don't + //use Read here). + if (mode == ZipArchiveMode.Create) { - //This is a bit of a hack and should be refactored - I am - //doing a similar foreach loop for both modes, but for Create - //I am doing very little work while Update gets a lot of - //code. This also does not handle any other mode (of - //which there currently wouldn't be one since we don't - //use Read here). - if (mode == ZipArchiveMode.Create) + foreach (string file in files) { - foreach (string file in files) - { - //Adds the file to the archive - zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); - } + //Adds the file to the archive + zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); } - else + } + else + { + foreach (string file in files) { - foreach (string file in files) + var fileInZip = (from f in zipFile.Entries + where f.Name == Path.GetFileName(file) + select f).FirstOrDefault(); + + switch (fileOverwrite) { - var fileInZip = (from f in zipFile.Entries - where f.Name == Path.GetFileName(file) - select f).FirstOrDefault(); + case Overwrite.Always: + //Deletes the file if it is found + if (fileInZip != null) + { + fileInZip.Delete(); + } - switch (fileOverwrite) - { - case Overwrite.Always: - //Deletes the file if it is found - if (fileInZip != null) - { - fileInZip.Delete(); - } + //Adds the file to the archive + zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); - //Adds the file to the archive - zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); - - break; - case Overwrite.IfNewer: - //This is a bit trickier - we only delete the file if it is - //newer, but if it is newer or if the file isn't already in - //the zip file, we will write it to the zip file - if (fileInZip != null) + break; + case Overwrite.IfNewer: + //This is a bit trickier - we only delete the file if it is + //newer, but if it is newer or if the file isn't already in + //the zip file, we will write it to the zip file + if (fileInZip != null) + { + //Deletes the file only if it is older than our file. + //Note that the file will be ignored if the existing file + //in the archive is newer. + if (fileInZip.LastWriteTime < File.GetLastWriteTime(file)) { - //Deletes the file only if it is older than our file. - //Note that the file will be ignored if the existing file - //in the archive is newer. - if (fileInZip.LastWriteTime < File.GetLastWriteTime(file)) - { - fileInZip.Delete(); + fileInZip.Delete(); - //Adds the file to the archive - zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); - } - } - else - { - //The file wasn't already in the zip file so add it to the archive + //Adds the file to the archive zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); } - break; - case Overwrite.Never: - //Don't do anything - this is a decision that you need to - //consider, however, since this will mean that no file will - //be written. You could write a second copy to the zip with - //the same name (not sure that is wise, however). - break; - } + } + else + { + //The file wasn't already in the zip file so add it to the archive + zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression); + } + break; + case Overwrite.Never: + //Don't do anything - this is a decision that you need to + //consider, however, since this will mean that no file will + //be written. You could write a second copy to the zip with + //the same name (not sure that is wise, however). + break; } } } @@ -311,15 +307,11 @@ namespace UVtools.Core.Extensions } if (string.IsNullOrEmpty(content)) return entry; - using (Stream stream = entry.Open()) - { - if (mode == ZipArchiveMode.Update) stream.SetLength(0); - using (TextWriter tw = new StreamWriter(stream)) - { - tw.Write(content); - tw.Close(); - } - } + using var stream = entry.Open(); + if (mode == ZipArchiveMode.Update) stream.SetLength(0); + using TextWriter tw = new StreamWriter(stream); + tw.Write(content); + tw.Close(); return entry; } @@ -343,13 +335,11 @@ namespace UVtools.Core.Extensions entry = input.CreateEntry(filename); } - if (ReferenceEquals(content, null)) return entry; - using (Stream stream = entry.Open()) - { - if (mode == ZipArchiveMode.Update) stream.SetLength(0); - stream.Write(content, 0, content.Length); - stream.Close(); - } + if (content is null) return entry; + using var stream = entry.Open(); + if (mode == ZipArchiveMode.Update) stream.SetLength(0); + stream.Write(content, 0, content.Length); + stream.Close(); return entry; } } diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs index d17adab..e7ca005 100644 --- a/UVtools.Core/FileFormats/SL1File.cs +++ b/UVtools.Core/FileFormats/SL1File.cs @@ -180,7 +180,7 @@ namespace UVtools.Core.FileFormats public float SupportHeadPenetration { get; set; } public float SupportHeadWidth { get; set; } - public byte SupportPillarWideningFactor { set; get; } + public ushort SupportPillarWideningFactor { set; get; } public float SupportPillarDiameter { get; set; } public string SupportSmallPillarDiameterPercent { get; set; } public float SupportMaxBridgesOnPillar { get; set; } @@ -198,7 +198,7 @@ namespace UVtools.Core.FileFormats public float SupportMaxPillarLinkDistance { get; set; } - public byte SupportPointsDensityRelative { get; set; } + public ushort SupportPointsDensityRelative { get; set; } public float SupportPointsMinimalDistance { get; set; } #endregion diff --git a/UVtools.Core/Managers/ClipboardManager.cs b/UVtools.Core/Managers/ClipboardManager.cs index 6577f6b..e26dda3 100644 --- a/UVtools.Core/Managers/ClipboardManager.cs +++ b/UVtools.Core/Managers/ClipboardManager.cs @@ -70,7 +70,7 @@ namespace UVtools.Core.Managers { #region Properties - public ObservableCollection Items { get; } = new(); + public RangeObservableCollection Items { get; } = new(); public FileFormat SlicerFile { get; set; } diff --git a/UVtools.Core/Managers/MaterialManager.cs b/UVtools.Core/Managers/MaterialManager.cs index 461e7b3..397a633 100644 --- a/UVtools.Core/Managers/MaterialManager.cs +++ b/UVtools.Core/Managers/MaterialManager.cs @@ -39,13 +39,13 @@ namespace UVtools.Core.Managers #region Members - private ObservableCollection _materials = new(); + private RangeObservableCollection _materials = new(); #endregion #region Properties - public ObservableCollection Materials + public RangeObservableCollection Materials { get => _materials; set => RaiseAndSetIfChanged(ref _materials, value); @@ -184,6 +184,11 @@ namespace UVtools.Core.Managers if (save) Save(); } + public void RemoveRange(IEnumerable collection) + { + _materials.RemoveRange(collection); + } + public int Count => _materials.Count; public bool IsReadOnly => false; public int IndexOf(Material item) => _materials.IndexOf(item); @@ -266,13 +271,11 @@ namespace UVtools.Core.Managers } } - #endregion - public void SortByName() { - var materials = _materials.ToList(); - materials.Sort((material, material1) => string.Compare(material.Name, material1.Name, StringComparison.Ordinal)); - Materials = new ObservableCollection(materials); + _materials.Sort((material, material1) => string.Compare(material.Name, material1.Name, StringComparison.Ordinal)); } + + #endregion } } \ No newline at end of file diff --git a/UVtools.Core/Objects/RangeObservableCollection.cs b/UVtools.Core/Objects/RangeObservableCollection.cs new file mode 100644 index 0000000..2c52e67 --- /dev/null +++ b/UVtools.Core/Objects/RangeObservableCollection.cs @@ -0,0 +1,703 @@ +namespace System.Collections.ObjectModel +{ + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + // See the LICENSE file in the project root for more information. + + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Linq; + + /// + /// Implementation of a dynamic data collection based on generic Collection<T>, + /// implementing INotifyCollectionChanged to notify listeners + /// when items get added, removed or the whole list is refreshed. + /// + public class RangeObservableCollection : ObservableCollection + { + //------------------------------------------------------ + // + // Private Fields + // + //------------------------------------------------------ + + #region Private Fields + [NonSerialized] + private DeferredEventsCollection? _deferredEvents; + #endregion Private Fields + + + //------------------------------------------------------ + // + // Constructors + // + //------------------------------------------------------ + + #region Constructors + /// + /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. + /// + public RangeObservableCollection() { } + + /// + /// Initializes a new instance of the ObservableCollection class that contains + /// elements copied from the specified collection and has sufficient capacity + /// to accommodate the number of elements copied. + /// + /// The collection whose elements are copied to the new list. + /// + /// The elements are copied onto the ObservableCollection in the + /// same order they are read by the enumerator of the collection. + /// + /// collection is a null reference + public RangeObservableCollection(IEnumerable collection) : base(collection) { } + + /// + /// Initializes a new instance of the ObservableCollection class + /// that contains elements copied from the specified list + /// + /// The list whose elements are copied to the new list. + /// + /// The elements are copied onto the ObservableCollection in the + /// same order they are read by the enumerator of the list. + /// + /// list is a null reference + public RangeObservableCollection(List list) : base(list) { } + + #endregion Constructors + + //------------------------------------------------------ + // + // Public Properties + // + //------------------------------------------------------ + + #region Public Properties + EqualityComparer? _Comparer; + public EqualityComparer Comparer + { + get => _Comparer ??= EqualityComparer.Default; + private set => _Comparer = value; + } + + /// + /// Gets or sets a value indicating whether this collection acts as a , + /// disallowing duplicate items, based on . + /// This might indeed consume background performance, but in the other hand, + /// it will pay off in UI performance as less required UI updates are required. + /// + public bool AllowDuplicates { get; set; } = true; + + #endregion Public Properties + + //------------------------------------------------------ + // + // Public Methods + // + //------------------------------------------------------ + + #region Public Methods + + /// + /// Clear the list and set a collection in place + /// + /// + /// The collection whose elements should be added to the end of the . + /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. + /// + /// is null. + public void ReplaceCollection(IEnumerable collection) + { + Clear(); + AddRange(collection); + } + + /// + /// Adds the elements of the specified collection to the end of the . + /// + /// + /// The collection whose elements should be added to the end of the . + /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. + /// + /// is null. + public void AddRange(IEnumerable collection) + { + InsertRange(Count, collection); + } + + /// + /// Inserts the elements of a collection into the at the specified index. + /// + /// The zero-based index at which the new elements should be inserted. + /// The collection whose elements should be inserted into the List. + /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. + /// is null. + /// is not in the collection range. + public void InsertRange(int index, IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (!AllowDuplicates) + collection = + collection + .Distinct(Comparer) + .Where(item => !Items.Contains(item, Comparer)) + .ToList(); + + if (collection is ICollection countable) + { + if (countable.Count == 0) + return; + } + else if (!collection.Any()) + return; + + CheckReentrancy(); + + //expand the following couple of lines when adding more constructors. + var target = (List)Items; + target.InsertRange(index, collection); + + OnEssentialPropertiesChanged(); + + /*if (!(collection is IList list)) + list = new List(collection); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, list, index));*/ + OnCollectionReset(); + } + + + /// + /// Removes the first occurence of each item in the specified collection from the . + /// + /// The items to remove. + /// is null. + public void RemoveRange(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + if (Count == 0) + return; + else if (collection is ICollection countable) + { + switch (countable.Count) + { + case 0: + return; + case 1: + { + using IEnumerator enumerator = countable.GetEnumerator(); + enumerator.MoveNext(); + Remove(enumerator.Current); + return; + } + } + } + else if (!collection.Any()) + return; + + CheckReentrancy(); + + //var clusters = new Dictionary>(); + //var lastIndex = -1; + //List? lastCluster = null; + foreach (T item in collection) + { + var index = IndexOf(item); + if (index < 0) + continue; + + Items.RemoveAt(index); + + /*if (lastIndex == index && lastCluster != null) + lastCluster.Add(item); + else + clusters[lastIndex = index] = lastCluster = new List { item };*/ + } + + OnEssentialPropertiesChanged(); + OnCollectionReset(); + + /*if (Count == 0) + OnCollectionReset(); + else + foreach (var cluster in clusters) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));*/ + + } + + /// + /// Iterates over the collection and removes all items that satisfy the specified match. + /// + /// The complexity is O(n). + /// + /// Returns the number of elements that where + /// is null. + public int RemoveAll(Predicate match) + { + return RemoveAll(0, Count, match); + } + + /// + /// Iterates over the specified range within the collection and removes all items that satisfy the specified match. + /// + /// The complexity is O(n). + /// The index of where to start performing the search. + /// The number of items to iterate on. + /// + /// Returns the number of elements that where + /// is out of range. + /// is out of range. + /// is null. + public int RemoveAll(int index, int count, Predicate match) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (index + count > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + if (match == null) + throw new ArgumentNullException(nameof(match)); + + if (Count == 0) + return 0; + + //List? cluster = null; + var clusterIndex = -1; + var removedCount = 0; + + using (BlockReentrancy()) + using (DeferEvents()) + { + for (var i = 0; i < count; i++, index++) + { + T item = Items[index]; + if (match(item)) + { + Items.RemoveAt(index); + removedCount++; + + /*if (clusterIndex == index) + { + Debug.Assert(cluster != null); + cluster!.Add(item); + } + else + { + cluster = new List { item }; + clusterIndex = index; + }*/ + + index--; + } + /*else if (clusterIndex > -1) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex)); + clusterIndex = -1; + cluster = null; + }*/ + } + + //if (clusterIndex > -1) + // OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex)); + } + + if (removedCount > 0) + { + OnEssentialPropertiesChanged(); + OnCollectionReset(); + } + + return removedCount; + } + + /// + /// Removes a range of elements from the >. + /// + /// The zero-based starting index of the range of elements to remove. + /// The number of elements to remove. + /// The specified range is exceeding the collection. + public void RemoveRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (index + count > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (count == 0) + return; + + if (count == 1) + { + RemoveItem(index); + return; + } + + //Items will always be List, see constructors + var items = (List)Items; + List removedItems = items.GetRange(index, count); + + CheckReentrancy(); + + items.RemoveRange(index, count); + + OnEssentialPropertiesChanged(); + + if (Count == 0) + OnCollectionReset(); + else + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index)); + } + + /// + /// Clears the current collection and replaces it with the specified collection, + /// using . + /// + /// The items to fill the collection with, after clearing it. + /// is null. + public void ReplaceRange(IEnumerable collection) + { + ReplaceRange(0, Count, collection); + } + + /// + /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact. + /// + /// The index of where to start the replacement. + /// The number of items to be replaced. + /// The collection to insert in that location. + /// is out of range. + /// is out of range. + /// is null. + /// is null. + public void ReplaceRange(int index, int count, IEnumerable collection) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (index + count > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + if (!AllowDuplicates) + collection = + collection + .Distinct(Comparer) + .ToList(); + + if (collection is ICollection countable) + { + if (countable.Count == 0) + { + RemoveRange(index, count); + return; + } + } + else if (!collection.Any()) + { + RemoveRange(index, count); + return; + } + + if (index + count == 0) + { + InsertRange(0, collection); + return; + } + + if (!(collection is IList list)) + list = new List(collection); + + using (BlockReentrancy()) + using (DeferEvents()) + { + var rangeCount = index + count; + var addedCount = list.Count; + + var changesMade = false; + List? + newCluster = null, + oldCluster = null; + + + int i = index; + for (; i < rangeCount && i - index < addedCount; i++) + { + //parallel position + T old = this[i], @new = list[i - index]; + if (Comparer.Equals(old, @new)) + { + OnRangeReplaced(i, newCluster!, oldCluster!); + continue; + } + else + { + Items[i] = @new; + + if (newCluster == null) + { + Debug.Assert(oldCluster == null); + newCluster = new List { @new }; + oldCluster = new List { old }; + } + else + { + newCluster.Add(@new); + oldCluster!.Add(old); + } + + changesMade = true; + } + } + + OnRangeReplaced(i, newCluster!, oldCluster!); + + //exceeding position + if (count != addedCount) + { + var items = (List)Items; + if (count > addedCount) + { + var removedCount = rangeCount - addedCount; + T[] removed = new T[removedCount]; + items.CopyTo(i, removed, 0, removed.Length); + items.RemoveRange(i, removedCount); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, i)); + } + else + { + var k = i - index; + T[] added = new T[addedCount - k]; + for (int j = k; j < addedCount; j++) + { + T @new = list[j]; + added[j - k] = @new; + } + items.InsertRange(i, added); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i)); + } + + OnEssentialPropertiesChanged(); + } + else if (changesMade) + { + OnIndexerPropertyChanged(); + } + } + } + + public void Sort() + { + Sort(null); + } + + public void Sort(Comparison comparison) + { + if (Count == 0) + { + return; + } + + var list = this.ToList(); + list.Sort(comparison); + ReplaceCollection(list); + } + + #endregion Public Methods + + + //------------------------------------------------------ + // + // Protected Methods + // + //------------------------------------------------------ + + #region Protected Methods + + /// + /// Called by base class Collection<T> when the list is being cleared; + /// raises a CollectionChanged event to any listeners. + /// + protected override void ClearItems() + { + if (Count == 0) + return; + + //CheckReentrancy(); + base.ClearItems(); + //OnEssentialPropertiesChanged(); + //OnCollectionReset(); + } + + /// + protected override void InsertItem(int index, T item) + { + if (!AllowDuplicates && Items.Contains(item)) + return; + + base.InsertItem(index, item); + } + + /// + protected override void SetItem(int index, T item) + { + if (AllowDuplicates) + { + if (Comparer.Equals(this[index], item)) + return; + } + else + if (Items.Contains(item, Comparer)) + return; + + CheckReentrancy(); + T oldItem = this[index]; + base.SetItem(index, item); + + OnIndexerPropertyChanged(); + OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index); + } + + /// + /// Raise CollectionChanged event to any listeners. + /// Properties/methods modifying this ObservableCollection will raise + /// a collection changed event through this virtual method. + /// + /// + /// When overriding this method, either call its base implementation + /// or call to guard against reentrant collection changes. + /// + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (_deferredEvents != null) + { + _deferredEvents.Add(e); + return; + } + base.OnCollectionChanged(e); + } + + protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this); + + #endregion Protected Methods + + + //------------------------------------------------------ + // + // Private Methods + // + //------------------------------------------------------ + + #region Private Methods + + /// + /// Helper to raise Count property and the Indexer property. + /// + void OnEssentialPropertiesChanged() + { + OnPropertyChanged(EventArgsCache.CountPropertyChanged); + OnIndexerPropertyChanged(); + } + + /// + /// /// Helper to raise a PropertyChanged event for the Indexer property + /// /// + void OnIndexerPropertyChanged() => + OnPropertyChanged(EventArgsCache.IndexerPropertyChanged); + + /// + /// Helper to raise CollectionChanged event to any listeners + /// + void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => + OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); + + /// + /// Helper to raise CollectionChanged event with action == Reset to any listeners + /// + void OnCollectionReset() => + OnCollectionChanged(EventArgsCache.ResetCollectionChanged); + + /// + /// Helper to raise event for clustered action and clear cluster. + /// + /// The index of the item following the replacement block. + /// + /// + //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable collection, IEqualityComparer comparer), + //move when supported language version updated. + void OnRangeReplaced(int followingItemIndex, ICollection newCluster, ICollection oldCluster) + { + if (oldCluster == null || oldCluster.Count == 0) + { + Debug.Assert(newCluster == null || newCluster.Count == 0); + return; + } + + OnCollectionChanged( + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + new List(newCluster), + new List(oldCluster), + followingItemIndex - oldCluster.Count)); + + oldCluster.Clear(); + newCluster.Clear(); + } + + #endregion Private Methods + + //------------------------------------------------------ + // + // Private Types + // + //------------------------------------------------------ + + #region Private Types + sealed class DeferredEventsCollection : List, IDisposable + { + readonly RangeObservableCollection _collection; + public DeferredEventsCollection(RangeObservableCollection collection) + { + Debug.Assert(collection != null); + Debug.Assert(collection._deferredEvents == null); + _collection = collection; + _collection._deferredEvents = this; + } + + public void Dispose() + { + _collection._deferredEvents = null; + foreach (var args in this) + _collection.OnCollectionChanged(args); + } + } + + #endregion Private Types + + } + + /// + /// To be kept outside , since otherwise, a new instance will be created for each generic type used. + /// + internal static class EventArgsCache + { + internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count"); + internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]"); + internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); + } +} \ No newline at end of file diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs index 7f60893..e039b8a 100644 --- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs +++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs @@ -12,7 +12,6 @@ using System.Collections.ObjectModel; using System.Drawing; using System.Linq; using System.Text; -using System.Threading.Tasks; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; @@ -46,7 +45,7 @@ namespace UVtools.Core.Operations private decimal _normalExposure = 12; private decimal _topBottomMargin = 5; private decimal _leftRightMargin = 10; - private byte _chamferLayers = 5; + private byte _chamferLayers = 0; private byte _erodeBottomIterations = 0; private decimal _partMargin = 0; private bool _enableAntiAliasing = true; @@ -88,7 +87,7 @@ namespace UVtools.Core.Operations private decimal _exposureGenManualLayerHeight; private decimal _exposureGenManualBottom; private decimal _exposureGenManualNormal; - private ObservableCollection _exposureTable = new(); + private RangeObservableCollection _exposureTable = new(); private CalibrateExposureFinderShapes _holeShape = CalibrateExposureFinderShapes.Square; #endregion @@ -680,7 +679,7 @@ namespace UVtools.Core.Operations public ExposureItem ExposureManualEntry => new (_exposureGenManualLayerHeight, _exposureGenManualBottom, _exposureGenManualNormal); - public ObservableCollection ExposureTable + public RangeObservableCollection ExposureTable { get => _exposureTable; set => RaiseAndSetIfChanged(ref _exposureTable, value); @@ -827,16 +826,14 @@ namespace UVtools.Core.Operations public void SortExposureTable() { - var list = _exposureTable.ToList(); - list.Sort(); - ExposureTable = new(list); + _exposureTable.Sort(); } public void SanitizeExposureTable() { List list = _exposureTable.ToList().Distinct().ToList(); list.Sort(); - ExposureTable = new(list); + _exposureTable.ReplaceCollection(list); } public void GenerateExposure() diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs index 9d123b4..9559d0b 100644 --- a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs +++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs @@ -69,8 +69,8 @@ namespace UVtools.Core.Operations private ExposureSetTypes _exposureSetType = ExposureSetTypes.Linear; private decimal _bottomExposureStep = 0.5m; private decimal _exposureStep = 0.2m; - private ObservableCollection _automaticExposureTable = new(); - private ObservableCollection _manualExposureTable = new(); + private RangeObservableCollection _automaticExposureTable = new(); + private RangeObservableCollection _manualExposureTable = new(); #endregion @@ -270,7 +270,7 @@ namespace UVtools.Core.Operations } [XmlIgnore] - public ObservableCollection AutomaticExposureTable + public RangeObservableCollection AutomaticExposureTable { get { @@ -280,14 +280,14 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _automaticExposureTable, value); } - public ObservableCollection ManualExposureTable + public RangeObservableCollection ManualExposureTable { get => _manualExposureTable; set => RaiseAndSetIfChanged(ref _manualExposureTable, value); } [XmlIgnore] - public ObservableCollection ExposureTable => IsExposureSetTypeManual ? _manualExposureTable : AutomaticExposureTable; + public RangeObservableCollection ExposureTable => IsExposureSetTypeManual ? _manualExposureTable : AutomaticExposureTable; /// /// Gets the exposure table into a dictionary where key is the layer height diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index 367e725..c1be566 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -46,7 +46,7 @@ namespace UVtools.Core.Operations private bool _extendBeyondLayerCount = true; private bool _discardUnmodifiedLayers; private ushort _stackMargin = 50; - private ObservableCollection _files = new(); + private RangeObservableCollection _files = new(); #endregion #region Overrides @@ -107,7 +107,7 @@ namespace UVtools.Core.Operations StringBuilder sb = new(); - if (Files.Count == 0) + if (_files.Count == 0) { sb.AppendLine("No files to import."); } @@ -161,13 +161,13 @@ namespace UVtools.Core.Operations } [XmlIgnore] - public ObservableCollection Files + public RangeObservableCollection Files { get => _files; set => RaiseAndSetIfChanged(ref _files, value); } - public uint Count => (uint) Files.Count; + public uint Count => (uint)_files.Count; #endregion #region Constructor @@ -183,19 +183,12 @@ namespace UVtools.Core.Operations public void AddFile(string file) { - Files.Add(new ValueDescription(file, Path.GetFileNameWithoutExtension(file))); + _files.Add(new ValueDescription(file, Path.GetFileNameWithoutExtension(file))); } public void Sort() { - var sortedFiles = Files.ToList(); - sortedFiles.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal)); - Files.Clear(); - foreach (var file in sortedFiles) - { - Files.Add(file); - } - //Files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1), Path.GetFileNameWithoutExtension(file2), StringComparison.Ordinal)); + _files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal)); } @@ -232,12 +225,12 @@ namespace UVtools.Core.Operations // Order raw images for (int i = 0; i < Count; i++) { - if (!Files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && - !Files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) && - !Files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) && - !Files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) && - !Files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue; - keyImage.Add(new KeyValuePair((uint)keyImage.Count, Files[i].ValueAsString)); + if (!_files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && + !_files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) && + !_files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) && + !_files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) && + !_files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue; + keyImage.Add(new KeyValuePair((uint)keyImage.Count, _files[i].ValueAsString)); } // Create virtual file format with images @@ -264,15 +257,15 @@ namespace UVtools.Core.Operations // Order remaining possible file formats for (int i = 0; i < Count; i++) { - if (Files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || - Files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) || - Files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) || - Files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || - Files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue; + if (_files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || + _files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) || + _files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) || + _files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || + _files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue; - var fileFormat = FileFormat.FindByExtension(Files[i].ValueAsString, true, true); + var fileFormat = FileFormat.FindByExtension(_files[i].ValueAsString, true, true); if (fileFormat is null) continue; - fileFormat.FileFullPath = Files[i].ValueAsString; + fileFormat.FileFullPath = _files[i].ValueAsString; fileFormats.Add(fileFormat); } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index f3e2fba..8dce5fe 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,14 +10,16 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, calibration, repair, conversion and manipulation - 2.9.1 + 2.9.2 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 + false true + @@ -46,7 +48,7 @@ - + diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs index b868cfd..a033dd9 100644 --- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs +++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs @@ -179,9 +179,6 @@ - - - @@ -200,9 +197,6 @@ - - - @@ -248,9 +242,6 @@ - - - @@ -347,15 +338,9 @@ - - - - - - diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index 7a454a1..f6854f0 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -18,7 +18,6 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; -using Avalonia.ThemeManager; using Emgu.CV; using UVtools.Core; using UVtools.Core.FileFormats; @@ -30,7 +29,7 @@ namespace UVtools.WPF { public class App : Application { - public static ThemeSelector ThemeSelector { get; set; } + //public static ThemeSelector ThemeSelector { get; set; } public static MainWindow MainWindow; public static FileFormat SlicerFile = null; diff --git a/UVtools.WPF/Assets/Styles/Styles.xaml b/UVtools.WPF/Assets/Styles/Styles.xaml index f86e52c..e11b340 100644 --- a/UVtools.WPF/Assets/Styles/Styles.xaml +++ b/UVtools.WPF/Assets/Styles/Styles.xaml @@ -5,10 +5,6 @@ - - @@ -33,13 +29,6 @@ - - -