diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-04-30 20:20:58 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-04-30 20:20:58 +0300 |
commit | 6d6be66ced23339f8d30f4aed2d32b83d14384af (patch) | |
tree | 80ba0c9891eabda762fc896f7d9fa74972535f07 /UVtools.Core | |
parent | 6cfedea4cc36200caab87301e0dd121e97ddc8f2 (diff) |
v2.9.2v2.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
Diffstat (limited to 'UVtools.Core')
-rw-r--r-- | UVtools.Core/Extensions/PathExtensions.cs | 8 | ||||
-rw-r--r-- | UVtools.Core/Extensions/StreamExtensions.cs | 8 | ||||
-rw-r--r-- | UVtools.Core/Extensions/StringExtensions.cs | 4 | ||||
-rw-r--r-- | UVtools.Core/Extensions/ZipArchiveExtensions.cs | 144 | ||||
-rw-r--r-- | UVtools.Core/FileFormats/SL1File.cs | 4 | ||||
-rw-r--r-- | UVtools.Core/Managers/ClipboardManager.cs | 2 | ||||
-rw-r--r-- | UVtools.Core/Managers/MaterialManager.cs | 17 | ||||
-rw-r--r-- | UVtools.Core/Objects/RangeObservableCollection.cs | 703 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationCalibrateExposureFinder.cs | 13 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationDynamicLayerHeight.cs | 10 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationLayerImport.cs | 45 | ||||
-rw-r--r-- | UVtools.Core/UVtools.Core.csproj | 6 |
12 files changed, 824 insertions, 140 deletions
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 /// <returns>Byte array data</returns> 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<T>(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); } /// <summary> @@ -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<ClipboardItem> Items { get; } = new(); + public RangeObservableCollection<ClipboardItem> 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<Material> _materials = new(); + private RangeObservableCollection<Material> _materials = new(); #endregion #region Properties - public ObservableCollection<Material> Materials + public RangeObservableCollection<Material> Materials { get => _materials; set => RaiseAndSetIfChanged(ref _materials, value); @@ -184,6 +184,11 @@ namespace UVtools.Core.Managers if (save) Save(); } + public void RemoveRange(IEnumerable<Material> 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<Material>(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; + + /// <summary> + /// 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. + /// </summary> + public class RangeObservableCollection<T> : ObservableCollection<T> + { + //------------------------------------------------------ + // + // Private Fields + // + //------------------------------------------------------ + + #region Private Fields + [NonSerialized] + private DeferredEventsCollection? _deferredEvents; + #endregion Private Fields + + + //------------------------------------------------------ + // + // Constructors + // + //------------------------------------------------------ + + #region Constructors + /// <summary> + /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. + /// </summary> + public RangeObservableCollection() { } + + /// <summary> + /// 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. + /// </summary> + /// <param name="collection">The collection whose elements are copied to the new list.</param> + /// <remarks> + /// The elements are copied onto the ObservableCollection in the + /// same order they are read by the enumerator of the collection. + /// </remarks> + /// <exception cref="ArgumentNullException"> collection is a null reference </exception> + public RangeObservableCollection(IEnumerable<T> collection) : base(collection) { } + + /// <summary> + /// Initializes a new instance of the ObservableCollection class + /// that contains elements copied from the specified list + /// </summary> + /// <param name="list">The list whose elements are copied to the new list.</param> + /// <remarks> + /// The elements are copied onto the ObservableCollection in the + /// same order they are read by the enumerator of the list. + /// </remarks> + /// <exception cref="ArgumentNullException"> list is a null reference </exception> + public RangeObservableCollection(List<T> list) : base(list) { } + + #endregion Constructors + + //------------------------------------------------------ + // + // Public Properties + // + //------------------------------------------------------ + + #region Public Properties + EqualityComparer<T>? _Comparer; + public EqualityComparer<T> Comparer + { + get => _Comparer ??= EqualityComparer<T>.Default; + private set => _Comparer = value; + } + + /// <summary> + /// Gets or sets a value indicating whether this collection acts as a <see cref="HashSet{T}"/>, + /// disallowing duplicate items, based on <see cref="Comparer"/>. + /// 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. + /// </summary> + public bool AllowDuplicates { get; set; } = true; + + #endregion Public Properties + + //------------------------------------------------------ + // + // Public Methods + // + //------------------------------------------------------ + + #region Public Methods + + /// <summary> + /// Clear the list and set a collection in place + /// </summary> + /// <param name="collection"> + /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>. + /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. + /// </param> + /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception> + public void ReplaceCollection(IEnumerable<T> collection) + { + Clear(); + AddRange(collection); + } + + /// <summary> + /// Adds the elements of the specified collection to the end of the <see cref="ObservableCollection{T}"/>. + /// </summary> + /// <param name="collection"> + /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>. + /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. + /// </param> + /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception> + public void AddRange(IEnumerable<T> collection) + { + InsertRange(Count, collection); + } + + /// <summary> + /// Inserts the elements of a collection into the <see cref="ObservableCollection{T}"/> at the specified index. + /// </summary> + /// <param name="index">The zero-based index at which the new elements should be inserted.</param> + /// <param name="collection">The collection whose elements should be inserted into the List<T>. + /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param> + /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception> + public void InsertRange(int index, IEnumerable<T> 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<T> 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<T>)Items; + target.InsertRange(index, collection); + + OnEssentialPropertiesChanged(); + + /*if (!(collection is IList list)) + list = new List<T>(collection); + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, list, index));*/ + OnCollectionReset(); + } + + + /// <summary> + /// Removes the first occurence of each item in the specified collection from the <see cref="ObservableCollection{T}"/>. + /// </summary> + /// <param name="collection">The items to remove.</param> + /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception> + public void RemoveRange(IEnumerable<T> collection) + { + if (collection == null) + throw new ArgumentNullException(nameof(collection)); + + if (Count == 0) + return; + else if (collection is ICollection<T> countable) + { + switch (countable.Count) + { + case 0: + return; + case 1: + { + using IEnumerator<T> enumerator = countable.GetEnumerator(); + enumerator.MoveNext(); + Remove(enumerator.Current); + return; + } + } + } + else if (!collection.Any()) + return; + + CheckReentrancy(); + + //var clusters = new Dictionary<int, List<T>>(); + //var lastIndex = -1; + //List<T>? 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<T> { item };*/ + } + + OnEssentialPropertiesChanged(); + OnCollectionReset(); + + /*if (Count == 0) + OnCollectionReset(); + else + foreach (var cluster in clusters) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));*/ + + } + + /// <summary> + /// Iterates over the collection and removes all items that satisfy the specified match. + /// </summary> + /// <remarks>The complexity is O(n).</remarks> + /// <param name="match"></param> + /// <returns>Returns the number of elements that where </returns> + /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception> + public int RemoveAll(Predicate<T> match) + { + return RemoveAll(0, Count, match); + } + + /// <summary> + /// Iterates over the specified range within the collection and removes all items that satisfy the specified match. + /// </summary> + /// <remarks>The complexity is O(n).</remarks> + /// <param name="index">The index of where to start performing the search.</param> + /// <param name="count">The number of items to iterate on.</param> + /// <param name="match"></param> + /// <returns>Returns the number of elements that where </returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception> + /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception> + public int RemoveAll(int index, int count, Predicate<T> 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<T>? 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<T> { 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; + } + + /// <summary> + /// Removes a range of elements from the <see cref="ObservableCollection{T}"/>>. + /// </summary> + /// <param name="index">The zero-based starting index of the range of elements to remove.</param> + /// <param name="count">The number of elements to remove.</param> + /// <exception cref="ArgumentOutOfRangeException">The specified range is exceeding the collection.</exception> + 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<T>, see constructors + var items = (List<T>)Items; + List<T> removedItems = items.GetRange(index, count); + + CheckReentrancy(); + + items.RemoveRange(index, count); + + OnEssentialPropertiesChanged(); + + if (Count == 0) + OnCollectionReset(); + else + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index)); + } + + /// <summary> + /// Clears the current collection and replaces it with the specified collection, + /// using <see cref="Comparer"/>. + /// </summary> + /// <param name="collection">The items to fill the collection with, after clearing it.</param> + /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception> + public void ReplaceRange(IEnumerable<T> collection) + { + ReplaceRange(0, Count, collection); + } + + /// <summary> + /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact. + /// </summary> + /// <param name="index">The index of where to start the replacement.</param> + /// <param name="count">The number of items to be replaced.</param> + /// <param name="collection">The collection to insert in that location.</param> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception> + /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception> + /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception> + public void ReplaceRange(int index, int count, IEnumerable<T> 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<T> 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<T> list)) + list = new List<T>(collection); + + using (BlockReentrancy()) + using (DeferEvents()) + { + var rangeCount = index + count; + var addedCount = list.Count; + + var changesMade = false; + List<T>? + 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<T> { @new }; + oldCluster = new List<T> { old }; + } + else + { + newCluster.Add(@new); + oldCluster!.Add(old); + } + + changesMade = true; + } + } + + OnRangeReplaced(i, newCluster!, oldCluster!); + + //exceeding position + if (count != addedCount) + { + var items = (List<T>)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<T> comparison) + { + if (Count == 0) + { + return; + } + + var list = this.ToList(); + list.Sort(comparison); + ReplaceCollection(list); + } + + #endregion Public Methods + + + //------------------------------------------------------ + // + // Protected Methods + // + //------------------------------------------------------ + + #region Protected Methods + + /// <summary> + /// Called by base class Collection<T> when the list is being cleared; + /// raises a CollectionChanged event to any listeners. + /// </summary> + protected override void ClearItems() + { + if (Count == 0) + return; + + //CheckReentrancy(); + base.ClearItems(); + //OnEssentialPropertiesChanged(); + //OnCollectionReset(); + } + + /// <inheritdoc/> + protected override void InsertItem(int index, T item) + { + if (!AllowDuplicates && Items.Contains(item)) + return; + + base.InsertItem(index, item); + } + + /// <inheritdoc/> + 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); + } + + /// <summary> + /// Raise CollectionChanged event to any listeners. + /// Properties/methods modifying this ObservableCollection will raise + /// a collection changed event through this virtual method. + /// </summary> + /// <remarks> + /// When overriding this method, either call its base implementation + /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes. + /// </remarks> + 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 + + /// <summary> + /// Helper to raise Count property and the Indexer property. + /// </summary> + void OnEssentialPropertiesChanged() + { + OnPropertyChanged(EventArgsCache.CountPropertyChanged); + OnIndexerPropertyChanged(); + } + + /// <summary> + /// /// Helper to raise a PropertyChanged event for the Indexer property + /// /// </summary> + void OnIndexerPropertyChanged() => + OnPropertyChanged(EventArgsCache.IndexerPropertyChanged); + + /// <summary> + /// Helper to raise CollectionChanged event to any listeners + /// </summary> + void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => + OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); + + /// <summary> + /// Helper to raise CollectionChanged event with action == Reset to any listeners + /// </summary> + void OnCollectionReset() => + OnCollectionChanged(EventArgsCache.ResetCollectionChanged); + + /// <summary> + /// Helper to raise event for clustered action and clear cluster. + /// </summary> + /// <param name="followingItemIndex">The index of the item following the replacement block.</param> + /// <param name="newCluster"></param> + /// <param name="oldCluster"></param> + //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer), + //move when supported language version updated. + void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster) + { + if (oldCluster == null || oldCluster.Count == 0) + { + Debug.Assert(newCluster == null || newCluster.Count == 0); + return; + } + + OnCollectionChanged( + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + new List<T>(newCluster), + new List<T>(oldCluster), + followingItemIndex - oldCluster.Count)); + + oldCluster.Clear(); + newCluster.Clear(); + } + + #endregion Private Methods + + //------------------------------------------------------ + // + // Private Types + // + //------------------------------------------------------ + + #region Private Types + sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable + { + readonly RangeObservableCollection<T> _collection; + public DeferredEventsCollection(RangeObservableCollection<T> 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 + + } + + /// <remarks> + /// To be kept outside <see cref="ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used. + /// </remarks> + 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<ExposureItem> _exposureTable = new(); + private RangeObservableCollection<ExposureItem> _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<ExposureItem> ExposureTable + public RangeObservableCollection<ExposureItem> 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<ExposureItem> 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<ExposureItem> _automaticExposureTable = new(); - private ObservableCollection<ExposureItem> _manualExposureTable = new(); + private RangeObservableCollection<ExposureItem> _automaticExposureTable = new(); + private RangeObservableCollection<ExposureItem> _manualExposureTable = new(); #endregion @@ -270,7 +270,7 @@ namespace UVtools.Core.Operations } [XmlIgnore] - public ObservableCollection<ExposureItem> AutomaticExposureTable + public RangeObservableCollection<ExposureItem> AutomaticExposureTable { get { @@ -280,14 +280,14 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _automaticExposureTable, value); } - public ObservableCollection<ExposureItem> ManualExposureTable + public RangeObservableCollection<ExposureItem> ManualExposureTable { get => _manualExposureTable; set => RaiseAndSetIfChanged(ref _manualExposureTable, value); } [XmlIgnore] - public ObservableCollection<ExposureItem> ExposureTable => IsExposureSetTypeManual ? _manualExposureTable : AutomaticExposureTable; + public RangeObservableCollection<ExposureItem> ExposureTable => IsExposureSetTypeManual ? _manualExposureTable : AutomaticExposureTable; /// <summary> /// 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<ValueDescription> _files = new(); + private RangeObservableCollection<ValueDescription> _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<ValueDescription> Files + public RangeObservableCollection<ValueDescription> 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, string>((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, string>((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 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.9.1</Version> + <Version>2.9.2</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> + <SignAssembly>false</SignAssembly> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <DocumentationFile></DocumentationFile> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -46,7 +48,7 @@ <ItemGroup> <PackageReference Include="BinarySerializer" Version="8.6.0" /> <PackageReference Include="Emgu.CV" Version="4.5.1.4349" /> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.10.0-1.final" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.10.0-2.final" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.10" /> <PackageReference Include="System.Memory" Version="4.5.4" /> |