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 | |
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
38 files changed, 1056 insertions, 304 deletions
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 /// <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" /> 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 @@ <Component Id="owcF8D46916EBAD015C45AD96CFA020065B" Guid="e65fa3f1-f330-eee1-9307-9a4d0835d4c2"> <File Id="owfF8D46916EBAD015C45AD96CFA020065B" Source="$(var.SourceDir)\Avalonia.Markup.Xaml.dll" KeyPath="yes" /> </Component> - <Component Id="owc2360F1EA9FE0B1355D5D44A01B139070" Guid="5f41891e-50bf-de85-90a5-56cbc18299b8"> - <File Id="owf2360F1EA9FE0B1355D5D44A01B139070" Source="$(var.SourceDir)\Avalonia.Markup.Xaml.Loader.dll" KeyPath="yes" /> - </Component> <Component Id="owc6834F87221790516C5BC358659FB168A" Guid="a648aa0a-edab-e191-afb0-5744596ea468"> <File Id="owf6834F87221790516C5BC358659FB168A" Source="$(var.SourceDir)\Avalonia.MicroCom.dll" KeyPath="yes" /> </Component> @@ -200,9 +197,6 @@ <Component Id="owcA08BB73AE310A316EDFF2172A1050F82" Guid="07375671-126d-c2a6-be8f-5b5f6d2891a1"> <File Id="owfA08BB73AE310A316EDFF2172A1050F82" Source="$(var.SourceDir)\Avalonia.Styling.dll" KeyPath="yes" /> </Component> - <Component Id="owc1FF8D55293A8E313D576AAE895471628" Guid="03163101-1cdb-415b-e8cc-ce48bb5a13fc"> - <File Id="owf1FF8D55293A8E313D576AAE895471628" Source="$(var.SourceDir)\Avalonia.ThemeManager.dll" KeyPath="yes" /> - </Component> <Component Id="owc0A621F7F94C102165C804D54ABDE7987" Guid="67ee26ea-3112-bccb-cb21-5160ed8c5ada"> <File Id="owf0A621F7F94C102165C804D54ABDE7987" Source="$(var.SourceDir)\Avalonia.Themes.Default.dll" KeyPath="yes" /> </Component> @@ -248,9 +242,6 @@ <Component Id="owc3883419A2BFFE7013C2194A99728AB11" Guid="dd137337-4b74-c76b-c87e-6b5f42697dd4"> <File Id="owf3883419A2BFFE7013C2194A99728AB11" Source="$(var.SourceDir)\dbgshim.dll" KeyPath="yes" /> </Component> - <Component Id="owc0810B538AB40CCE1552F9EF3F42953A3" Guid="f95c1be6-57ee-5b8f-1c0f-2f4e227d8d79"> - <File Id="owf0810B538AB40CCE1552F9EF3F42953A3" Source="$(var.SourceDir)\DynamicData.dll" KeyPath="yes" /> - </Component> <Component Id="owc63D81B12D4DED477220E84E2AEBD7DBE" Guid="b08152e4-c420-0b27-69c4-3f58ac0a5fc7"> <File Id="owf63D81B12D4DED477220E84E2AEBD7DBE" Source="$(var.SourceDir)\Emgu.CV.Platform.NetStandard.dll" KeyPath="yes" /> </Component> @@ -347,15 +338,9 @@ <Component Id="owc29161E83FC02784120326979FF285358" Guid="a8d30e75-1063-4d27-60b6-13d4bd4f52eb"> <File Id="owf29161E83FC02784120326979FF285358" Source="$(var.SourceDir)\opencv_videoio_ffmpeg451_64.dll" KeyPath="yes" /> </Component> - <Component Id="owcBFCA0BB8CBE4A68414FA088B4F7F3D22" Guid="babeb74f-9861-b222-a49a-ec268c3cd3ca"> - <File Id="owfBFCA0BB8CBE4A68414FA088B4F7F3D22" Source="$(var.SourceDir)\ReactiveUI.dll" KeyPath="yes" /> - </Component> <Component Id="owc363CAFE6342F1E71AB07D13BAEEE5FF0" Guid="8e946fdc-ea52-7c4b-a8b3-69e1ecf51086"> <File Id="owf363CAFE6342F1E71AB07D13BAEEE5FF0" Source="$(var.SourceDir)\SkiaSharp.dll" KeyPath="yes" /> </Component> - <Component Id="owcB6B3A58616D1CC4857D00CF30CE2144F" Guid="9e96df83-7e62-ac6c-85b4-2a92ac85ef1e"> - <File Id="owfB6B3A58616D1CC4857D00CF30CE2144F" Source="$(var.SourceDir)\Splat.dll" KeyPath="yes" /> - </Component> <Component Id="owc25FBE4153F32A5634B0ADA8A6954CC89" Guid="6c98bea6-1304-2fb8-4ce3-709ad7fb9601"> <File Id="owf25FBE4153F32A5634B0ADA8A6954CC89" Source="$(var.SourceDir)\System.AppContext.dll" KeyPath="yes" /> </Component> 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 @@ <Border Padding="20"></Border> </Design.PreviewWith> - <Style Selector="Expander[IsExpanded=true] /template/ ToggleButton#PART_toggle /template/ ContentPresenter#PART_ContentPresenter"> - <Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForeground}"/> - </Style> - <Style Selector="Border.GroupBox"> <Setter Property="BorderThickness" Value="4" /> </Style> @@ -33,13 +29,6 @@ <Setter Property="AcceptsTab" Value="True" /> </Style> - <Style Selector="RadioButton"> - <Setter Property="MinWidth" Value="30"></Setter> - </Style> - - <Style Selector="CheckBox"> - <Setter Property="MinWidth" Value="30"></Setter> - </Style> <Style Selector="NumericUpDown"> <Setter Property="MinWidth" Value="130"></Setter> diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs index 4a901ed..1da281f 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs +++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs @@ -1,12 +1,10 @@ using System; using System.Linq; using System.Timers; -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Threading; -using DynamicData; using MessageBox.Avalonia.Enums; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; @@ -148,7 +146,7 @@ namespace UVtools.WPF.Controls.Calibrators $"Are you sure you want to remove the {_exposureTable.SelectedItems.Count} selected entries?" ) != ButtonResult.Yes) return; - Operation.ExposureTable.RemoveMany(_exposureTable.SelectedItems.Cast<ExposureItem>()); + Operation.ExposureTable.RemoveRange(_exposureTable.SelectedItems.Cast<ExposureItem>()); } } } diff --git a/UVtools.WPF/Controls/KernelControl.axaml.cs b/UVtools.WPF/Controls/KernelControl.axaml.cs index c836545..3c8926b 100644 --- a/UVtools.WPF/Controls/KernelControl.axaml.cs +++ b/UVtools.WPF/Controls/KernelControl.axaml.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using Avalonia; using Avalonia.Markup.Xaml; -using DynamicData.Annotations; using Emgu.CV; using Emgu.CV.CvEnum; using UVtools.Core.Extensions; @@ -22,7 +20,7 @@ namespace UVtools.WPF.Controls private int _anchorX = -1; private int _anchorY = -1; private string _matrixText; - public List<ElementShape> KernelShapes { get; } = new List<ElementShape>(); + public List<ElementShape> KernelShapes { get; } = new(); public string MatrixText { diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs index 7381dd6..c0e52f5 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs @@ -1,18 +1,13 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; -using DynamicData; -using MessageBox.Avalonia.Enums; using UVtools.Core.FileFormats; using UVtools.Core.Objects; using UVtools.Core.Operations; -using UVtools.WPF.Extensions; using UVtools.WPF.Windows; namespace UVtools.WPF.Controls.Tools @@ -197,7 +192,7 @@ namespace UVtools.WPF.Controls.Tools public void RemoveFiles() { - Operation.Files.RemoveMany(FilesListBox.SelectedItems.OfType<ValueDescription>()); + Operation.Files.RemoveRange(FilesListBox.SelectedItems.OfType<ValueDescription>()); } public void ClearFiles() diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs index 8b40065..71d6a5f 100644 --- a/UVtools.WPF/MainWindow.Information.cs +++ b/UVtools.WPF/MainWindow.Information.cs @@ -31,15 +31,15 @@ namespace UVtools.WPF { public partial class MainWindow { - public ObservableCollection<SlicerProperty> SlicerProperties { get; } = new(); + public RangeObservableCollection<SlicerProperty> SlicerProperties { get; } = new(); public DataGrid PropertiesGrid; public DataGrid CurrentLayerGrid; private uint _visibleThumbnailIndex; private Bitmap _visibleThumbnailImage; - private ObservableCollection<ValueDescription> _currentLayerProperties = new(); + private RangeObservableCollection<ValueDescription> _currentLayerProperties = new(); - public ObservableCollection<ValueDescription> CurrentLayerProperties + public RangeObservableCollection<ValueDescription> CurrentLayerProperties { get => _currentLayerProperties; set => RaiseAndSetIfChanged(ref _currentLayerProperties, value); diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 4d29d55..c765f6f 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -8,20 +8,12 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; -using System.Drawing; -using System.IO; using System.Linq; using System.Threading.Tasks; -using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Input; -using Avalonia.Layout; using Avalonia.Threading; -using Avalonia.Utilities; -using DynamicData; using Emgu.CV; using Emgu.CV.Structure; using Emgu.CV.Util; @@ -31,14 +23,13 @@ using UVtools.Core.Extensions; using UVtools.Core.Operations; using UVtools.WPF.Extensions; using Brushes = Avalonia.Media.Brushes; -using Size = Avalonia.Size; namespace UVtools.WPF { public partial class MainWindow { #region Members - private ObservableCollection<LayerIssue> _issues = new ObservableCollection<LayerIssue>(); + private RangeObservableCollection<LayerIssue> _issues = new(); private bool _firstTimeOnIssues = true; public DataGrid IssuesGrid; @@ -46,7 +37,7 @@ namespace UVtools.WPF #endregion #region Properties - public ObservableCollection<LayerIssue> Issues + public RangeObservableCollection<LayerIssue> Issues { get => _issues; private set => RaiseAndSetIfChanged(ref _issues, value); @@ -66,6 +57,10 @@ namespace UVtools.WPF IssuesGrid.CellPointerPressed += IssuesGridOnCellPointerPressed; IssuesGrid.SelectionChanged += IssuesGridOnSelectionChanged; IssuesGrid.KeyUp += IssuesGridOnKeyUp; + Issues.CollectionChanged += (sender, e) => + { + UpdateLayerTrackerHighlightIssues(); + }; } public void IssueGoPrevious() @@ -214,7 +209,7 @@ namespace UVtools.WPF Clipboard.Clip($"Manually removed {issueRemoveList.Count} issues"); - Issues.RemoveMany(issueRemoveList); + Issues.RemoveRange(issueRemoveList); if (layersRemove.Count > 0) { @@ -255,7 +250,7 @@ namespace UVtools.WPF var list = IssuesGrid.SelectedItems.Cast<LayerIssue>().ToArray(); IgnoredIssues.AddRange(list); IssuesGrid.SelectedItems.Clear(); - Issues.RemoveMany(list); + Issues.RemoveRange(list); ShowLayer(); } @@ -321,9 +316,7 @@ namespace UVtools.WPF .ThenBy(issue => issue.LayerIndex) .ThenBy(issue => issue.PixelsCount).ToList(); - Issues.Clear(); - Issues.AddRange(issueList); - UpdateLayerTrackerHighlightIssues(); + Issues.ReplaceCollection(issueList); } public int IssueSelectedIndex @@ -475,12 +468,10 @@ namespace UVtools.WPF if (resultIssues is null) { - UpdateLayerTrackerHighlightIssues(); return; } Issues.AddRange(resultIssues); - UpdateLayerTrackerHighlightIssues(); - + ShowLayer(); RaisePropertyChanged(nameof(IssueSelectedIndexStr)); @@ -492,7 +483,7 @@ namespace UVtools.WPF public Dictionary<uint, uint> GetIssuesCountPerLayer() { if (Issues is null || Issues.Count == 0) return null; - Dictionary<uint, uint> layerIndexIssueCount = new Dictionary<uint, uint>(); + Dictionary<uint, uint> layerIndexIssueCount = new(); foreach (var issue in Issues) { if (!layerIndexIssueCount.ContainsKey(issue.LayerIndex)) @@ -532,7 +523,6 @@ namespace UVtools.WPF { Issues.Clear(); if(clearIgnored) IgnoredIssues.Clear(); - UpdateLayerTrackerHighlightIssues(); } diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs index 68e1137..af64239 100644 --- a/UVtools.WPF/MainWindow.LayerPreview.cs +++ b/UVtools.WPF/MainWindow.LayerPreview.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; @@ -684,7 +685,7 @@ namespace UVtools.WPF InvalidateLayerNavigation(); } - Stopwatch watch = Stopwatch.StartNew(); + var watch = Stopwatch.StartNew(); LayerCache.Layer = SlicerFile[_actualLayer]; try @@ -703,15 +704,59 @@ namespace UVtools.WPF } else if (_showLayerImageDifference) { - if (_actualLayer > 0 && _actualLayer < SlicerFile.LayerCount - 1) + //if (_actualLayer > 0 && _actualLayer < SlicerFile.LayerCount - 1) + // { + var previousLayer = _actualLayer > 0 ? SlicerFile[_actualLayer - 1] : null; + var nextLayer = _actualLayer < SlicerFile.LastLayerIndex ? SlicerFile[_actualLayer + 1] : null; + Mat previousImage = null; + Mat nextImage = null; + + // Optimize empties for now... + var rect = Rectangle.Empty; + if (!LayerCache.Layer.IsEmpty) { - Mat previousImage = null; - Mat nextImage = null; + rect = LayerCache.Layer.BoundingRectangle; + } + else if (previousLayer is not null && !previousLayer.IsEmpty) + { + rect = previousLayer.BoundingRectangle; + } + else if (nextLayer is not null && !nextLayer.IsEmpty) + { + rect = nextLayer.BoundingRectangle; + } + if (previousLayer is not null && !previousLayer.IsEmpty) + { + rect = Rectangle.Union(rect, previousLayer.BoundingRectangle); + } + if (nextLayer is not null && !nextLayer.IsEmpty) + { + rect = Rectangle.Union(rect, nextLayer.BoundingRectangle); + } + + /*var rect = Rectangle.Union( + Rectangle.Union(LayerCache.Layer.BoundingRectangle, previousLayer.BoundingRectangle), + nextLayer.BoundingRectangle);*/ + + if (!rect.IsEmpty && (previousLayer is not null || nextLayer is not null)) + { + byte* previousSpan = null; + byte* nextSpan = null; // Can improve performance on >4K images? Parallel.Invoke( - () => { previousImage = SlicerFile[_actualLayer - 1].LayerMat; }, - () => { nextImage = SlicerFile[_actualLayer + 1].LayerMat; }); + () => + { + if (previousLayer is null) return; + previousImage = previousLayer.LayerMat; + previousSpan = previousImage.GetBytePointer(); + }, + () => + { + if (nextLayer is null) return; + nextImage = nextLayer.LayerMat; + nextSpan = nextImage.GetBytePointer(); + }); /*using (var previousImage = SlicerFile[_actualLayer - 1].LayerMat) using (var nextImage = SlicerFile[_actualLayer + 1].LayerMat) @@ -719,43 +764,61 @@ namespace UVtools.WPF //var previousSpan = previousImage.GetPixelSpan<byte>(); //var nextSpan = nextImage.GetPixelSpan<byte>(); - var previousSpan = previousImage.GetBytePointer(); - var nextSpan = nextImage.GetBytePointer(); - int width = LayerCache.Image.Width; + int width = LayerCache.Image.Step; int channels = LayerCache.ImageBgr.NumberOfChannels; - Parallel.For(0, LayerCache.Image.Height, y => + bool showSimilarityInstead = + Settings.LayerPreview.LayerDifferenceHighlightSimilarityInstead; + + Parallel.For(rect.Y, rect.Bottom, y => { - for (int x = 0; x < width; x++) + for (int x = rect.X; x < rect.Right; x++) { int pixel = y * width + x; - if (imageSpan[pixel] != 0) continue; - Color color = Color.Empty; - if (previousSpan[pixel] > 0 && nextSpan[pixel] > 0) + if (showSimilarityInstead) { - color = Settings.LayerPreview.NextLayerDifferenceColor; + if (imageSpan[pixel] == 0) continue; } - else if (previousSpan[pixel] > 0) + else + { + if (imageSpan[pixel] != 0) continue; + } + + byte brightness = 0; + + var color = Color.Empty; + if (previousSpan is not null && nextSpan is not null && previousSpan[pixel] > 0 && nextSpan[pixel] > 0) { + brightness = Math.Max(previousSpan[pixel], nextSpan[pixel]); + color = Settings.LayerPreview.BothLayerDifferenceColor; + } + else if (previousSpan is not null && previousSpan[pixel] > 0) + { + brightness = previousSpan[pixel]; color = Settings.LayerPreview.PreviousLayerDifferenceColor; } - else if (nextSpan[pixel] > 0) + else if (nextSpan is not null && nextSpan[pixel] > 0) { + brightness = nextSpan[pixel]; color = Settings.LayerPreview.NextLayerDifferenceColor; } if (color.IsEmpty) continue; + + color = color.FactorColor(brightness); + var bgrPixel = pixel * channels; imageBgrSpan[bgrPixel] = color.B; // B - imageBgrSpan[++bgrPixel] = color.G; // G - imageBgrSpan[++bgrPixel] = color.R; // R + imageBgrSpan[bgrPixel + 1] = color.G; // G + imageBgrSpan[bgrPixel + 2] = color.R; // R //imageBgrSpan[++bgrPixel] = color.A; // A } }); - - previousImage.Dispose(); - nextImage.Dispose(); } + + previousImage?.Dispose(); + nextImage?.Dispose(); + // } } diff --git a/UVtools.WPF/MainWindow.Log.cs b/UVtools.WPF/MainWindow.Log.cs index 6540e12..4939772 100644 --- a/UVtools.WPF/MainWindow.Log.cs +++ b/UVtools.WPF/MainWindow.Log.cs @@ -14,7 +14,7 @@ namespace UVtools.WPF { public partial class MainWindow { - public ObservableCollection<LogItem> Logs { get; } = new ObservableCollection<LogItem>(); + public RangeObservableCollection<LogItem> Logs { get; } = new(); private bool _isVerbose; public bool IsVerbose diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs index 1c4f0a4..1a4d9b0 100644 --- a/UVtools.WPF/MainWindow.PixelEditor.cs +++ b/UVtools.WPF/MainWindow.PixelEditor.cs @@ -16,7 +16,6 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Media.Imaging; using Avalonia.Threading; -using DynamicData; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; @@ -31,15 +30,15 @@ namespace UVtools.WPF { public partial class MainWindow { - public ObservableCollection<PixelOperation> Drawings { get; } = new ObservableCollection<PixelOperation>(); + public RangeObservableCollection<PixelOperation> Drawings { get; } = new (); public DataGrid DrawingsGrid; private int _selectedPixelOperationTabIndex; - public PixelDrawing DrawingPixelDrawing { get; } = new PixelDrawing(); - public PixelText DrawingPixelText { get; } = new PixelText(); - public PixelEraser DrawingPixelEraser { get; } = new PixelEraser(); - public PixelSupport DrawingPixelSupport { get; } = new PixelSupport(); - public PixelDrainHole DrawingPixelDrainHole { get; } = new PixelDrainHole(); + public PixelDrawing DrawingPixelDrawing { get; } = new(); + public PixelText DrawingPixelText { get; } = new(); + public PixelEraser DrawingPixelEraser { get; } = new(); + public PixelSupport DrawingPixelSupport { get; } = new(); + public PixelDrainHole DrawingPixelDrainHole { get; } = new(); public int SelectedPixelOperationTabIndex { @@ -123,7 +122,7 @@ namespace UVtools.WPF public void OnClickDrawingRemove() { if (DrawingsGrid.SelectedItems.Count == 0) return; - Drawings.RemoveMany(DrawingsGrid.SelectedItems.Cast<PixelOperation>()); + Drawings.RemoveRange(DrawingsGrid.SelectedItems.Cast<PixelOperation>()); ShowLayer(); } @@ -145,9 +144,19 @@ namespace UVtools.WPF if ((keyModifiers & KeyModifiers.Control) != 0) { - var removeItems = Drawings.Where(item => + if (Drawings.RemoveAll(operation => { - Rectangle rect = new Rectangle(item.Location, item.Size); + Rectangle rect = new(operation.Location, operation.Size); + rect.X -= operation.Size.Width / 2; + rect.Y -= operation.Size.Height / 2; + return rect.Contains(realLocation); + }) > 0) + { + ShowLayer(); + } + /*var removeItems = Drawings.Where(item => + { + Rectangle rect = new(item.Location, item.Size); rect.X -= item.Size.Width / 2; rect.Y -= item.Size.Height / 2; return rect.Contains(realLocation); @@ -156,7 +165,7 @@ namespace UVtools.WPF { Drawings.RemoveMany(removeItems); ShowLayer(); - } + }*/ return; } @@ -176,7 +185,7 @@ namespace UVtools.WPF DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.BrushSize, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.RemovePixelBrightness, DrawingPixelDrawing.PixelBrightness, isAdd); //if (PixelHistory.Contains(operation)) continue; - Drawings.Add(operationDrawing); + AddDrawing(operationDrawing); if (layerIndex == _actualLayer) { @@ -257,7 +266,7 @@ namespace UVtools.WPF //if (PixelHistory.Contains(operation)) continue; //PixelHistory.Add(operation); - Drawings.Add(operationText); + AddDrawing(operationText); /*var color = isAdd ? Settings.PixelEditor.AddPixelColor : Settings.PixelEditor.RemovePixelColor; @@ -284,7 +293,7 @@ namespace UVtools.WPF var operationEraser = new PixelEraser(layerIndex, realLocation, DrawingPixelEraser.PixelBrightness); //if (PixelHistory.Contains(operation)) continue; - Drawings.Add(operationEraser); + AddDrawing(operationEraser); /*if (layerIndex == _actualLayer) { @@ -312,7 +321,7 @@ namespace UVtools.WPF DrawingPixelSupport.BaseDiameter, DrawingPixelSupport.PixelBrightness); //if (PixelHistory.Contains(operation)) return; - Drawings.Add(operationSupport); + AddDrawing(operationSupport); CvInvoke.Circle(LayerCache.ImageBgr, location, operationSupport.TipDiameter / 2, new MCvScalar(Settings.PixelEditor.SupportsColor.B, Settings.PixelEditor.SupportsColor.G, Settings.PixelEditor.SupportsColor.R), -1); @@ -324,7 +333,7 @@ namespace UVtools.WPF var operationDrainHole = new PixelDrainHole(ActualLayer, realLocation, DrawingPixelDrainHole.Diameter); //if (PixelHistory.Contains(operation)) return; - Drawings.Add(operationDrainHole); + AddDrawing(operationDrainHole); CvInvoke.Circle(LayerCache.ImageBgr, location, operationDrainHole.Diameter / 2, new MCvScalar(Settings.PixelEditor.DrainHoleColor.B, Settings.PixelEditor.DrainHoleColor.G, Settings.PixelEditor.DrainHoleColor.R), -1); @@ -336,6 +345,15 @@ namespace UVtools.WPF } } + public void AddDrawing(PixelOperation operation) + { + Drawings.Insert(0, operation); + for (int i = 0; i < Drawings.Count; i++) + { + Drawings[i].Index = (uint) (Drawings.Count - i); + } + } + public async void DrawModifications(bool exitEditor) { if (Drawings.Count == 0) diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 7b2f84e..019700e 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -532,6 +532,7 @@ CanUserSortColumns="True" GridLinesVisibility="Horizontal" IsReadOnly="True" + SelectionMode="Extended" ClipboardCopyMode="IncludeHeader" Items="{Binding SlicerProperties}"> <DataGrid.Columns> @@ -1583,8 +1584,6 @@ <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal"> - - </StackPanel> <DataGrid Items="{Binding Logs}" CanUserReorderColumns="True" @@ -1593,6 +1592,7 @@ GridLinesVisibility="All" IsReadOnly="True" ClipboardCopyMode="IncludeHeader" + SelectionMode="Extended" > <DataGrid.Columns> <DataGridTextColumn Header="#" @@ -1805,11 +1805,18 @@ IsChecked="{Binding ShowLayerImageDifference}" ToolTip.Tip="Show layer differences where darker pixels were also present on previous layer and the white pixels the difference between previous and current layer." VerticalAlignment="Stretch" - Margin="1,0,0,0" - > + Margin="1,0,0,0"> + <Button.ContextMenu> + <ContextMenu PlacementMode="Bottom"> + <CheckBox + Content="Show layer similarity instead of difference" + ToolTip.Tip="If enabled, it will recolor the current layer pixels in common with the previous and next layer" + IsChecked="{Binding Settings.LayerPreview.LayerDifferenceHighlightSimilarityInstead}"/> + </ContextMenu> + </Button.ContextMenu> <StackPanel Orientation="Horizontal"> <Image Source="/Assets/Icons/layers-16x16.png"/> - <TextBlock Margin="5,0,5,0" Text="Difference"/> + <TextBlock Margin="5,0,5,0" Text="Difference ⮟"/> </StackPanel> </ToggleButton> diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 0f2fbe8..07fc6f5 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -440,7 +440,7 @@ namespace UVtools.WPF { InitializeComponent(); - App.ThemeSelector?.EnableThemes(this); + //App.ThemeSelector?.EnableThemes(this); InitProgress(); InitInformation(); InitIssues(); diff --git a/UVtools.WPF/Structures/PEProfileFolder.cs b/UVtools.WPF/Structures/PEProfileFolder.cs index 5e26514..d0ce382 100644 --- a/UVtools.WPF/Structures/PEProfileFolder.cs +++ b/UVtools.WPF/Structures/PEProfileFolder.cs @@ -12,7 +12,7 @@ namespace UVtools.WPF.Structures { public class PEProfileFolder : BindableBase { - private ObservableCollection<CheckBox> _items = new ObservableCollection<CheckBox>(); + private RangeObservableCollection<CheckBox> _items = new (); private ushort _installed; private ushort _updates; @@ -27,7 +27,7 @@ namespace UVtools.WPF.Structures public string SourcePath { get; } public string TargetPath { get; } - public ObservableCollection<CheckBox> Items + public RangeObservableCollection<CheckBox> Items { get => _items; set => RaiseAndSetIfChanged(ref _items, value); diff --git a/UVtools.WPF/Structures/PixelPicker.cs b/UVtools.WPF/Structures/PixelPicker.cs index ae7f03b..a3d26a0 100644 --- a/UVtools.WPF/Structures/PixelPicker.cs +++ b/UVtools.WPF/Structures/PixelPicker.cs @@ -1,5 +1,4 @@ using System.Drawing; -using ReactiveUI; using UVtools.Core.Objects; namespace UVtools.WPF.Structures diff --git a/UVtools.WPF/Structures/SlicerProperty.cs b/UVtools.WPF/Structures/SlicerProperty.cs index 94cdc82..29b0814 100644 --- a/UVtools.WPF/Structures/SlicerProperty.cs +++ b/UVtools.WPF/Structures/SlicerProperty.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text; -using Avalonia; -using ReactiveUI; +using UVtools.Core.Objects; namespace UVtools.WPF.Structures { - public class SlicerProperty : ReactiveObject + public class SlicerProperty : BindableBase { private string _name; private string _value; @@ -16,19 +11,19 @@ namespace UVtools.WPF.Structures public string Name { get => _name; - set => this.RaiseAndSetIfChanged(ref _name, value); + set => RaiseAndSetIfChanged(ref _name, value); } public string Value { get => _value; - set => this.RaiseAndSetIfChanged(ref _value, value); + set => RaiseAndSetIfChanged(ref _value, value); } public string Group { get => _group; - set => this.RaiseAndSetIfChanged(ref _group, value); + set => RaiseAndSetIfChanged(ref _group, value); } public SlicerProperty(string name, string value, string group = null) diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index ccdc2f6..1ddef0a 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ <PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <RepositoryType>Git</RepositoryType> - <Version>2.9.1</Version> + <Version>2.9.2</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -24,15 +24,14 @@ <NoWarn>1701;1702;</NoWarn> </PropertyGroup> <ItemGroup> - <PackageReference Include="Avalonia" Version="0.10.0" /> + <PackageReference Include="Avalonia" Version="0.10.2" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> - <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.0" /> - <PackageReference Include="Avalonia.Desktop" Version="0.10.0" /> - <PackageReference Include="Avalonia.Diagnostics" Version="0.10.0" /> - <PackageReference Include="Avalonia.ThemeManager" Version="0.10.0" /> + <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.2" /> + <PackageReference Include="Avalonia.Desktop" Version="0.10.2" /> + <PackageReference Include="Avalonia.Diagnostics" Version="0.10.2" /> <PackageReference Include="Emgu.CV.runtime.windows" Version="4.5.1.4349" /> - <PackageReference Include="MessageBox.Avalonia" Version="1.1.1" /> - <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.0" /> + <PackageReference Include="MessageBox.Avalonia" Version="1.2.0" /> + <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.2" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\UVtools.Core\UVtools.Core.csproj" /> diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index 288e0fe..2ce94f2 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -172,32 +172,33 @@ namespace UVtools.WPF [Serializable] public sealed class LayerPreviewUserSettings : BindableBase { - private Color _tooltipOverlayBackgroundColor = new Color(210, 255, 255, 192); + private Color _tooltipOverlayBackgroundColor = new(210, 255, 255, 192); private bool _tooltipOverlay = true; - private Color _volumeBoundsOutlineColor = new Color(255, 0, 255, 0); + private Color _volumeBoundsOutlineColor = new(255, 0, 255, 0); private byte _volumeBoundsOutlineThickness = 3; private bool _volumeBoundsOutline = true; - private Color _layerBoundsOutlineColor = new Color(255, 0, 255, 0); + private Color _layerBoundsOutlineColor = new(255, 0, 255, 0); private byte _layerBoundsOutlineThickness = 3; private bool _layerBoundsOutline = false; - private Color _hollowOutlineColor = new Color(255, 255, 165, 0); + private Color _hollowOutlineColor = new(255, 255, 165, 0); private sbyte _hollowOutlineLineThickness = 5; private bool _hollowOutline = false; - private Color _maskOutlineColor = new Color(255, 42, 157, 244); + private Color _maskOutlineColor = new(255, 42, 157, 244); private sbyte _maskOutlineLineThickness = 10; private bool _maskClearRoiAfterSet = true; - private Color _previousLayerDifferenceColor = new Color(255, 255, 0, 255); - private Color _nextLayerDifferenceColor = new Color(255, 0, 255, 255); - private Color _bothLayerDifferenceColor = new Color(255, 255, 0, 0); + private Color _previousLayerDifferenceColor = new(255, 81, 131, 82); + private Color _nextLayerDifferenceColor = new(255, 81, 249, 252); + private Color _bothLayerDifferenceColor = new(255, 246, 240, 216); private bool _showLayerDifference = false; - private Color _islandColor = new Color(255, 255, 255, 0); - private Color _islandHighlightColor = new Color(255, 255, 215, 0); - private Color _overhangColor = new Color(255, 255, 105, 180); - private Color _overhangHighlightColor = new Color(255, 255, 20, 147); - private Color _resinTrapColor = new Color(255, 255, 165, 0); - private Color _resinTrapHighlightColor = new Color(255, 244, 164, 96); - private Color _touchingBoundsColor = new Color(255, 255, 0, 0); - private Color _crosshairColor = new Color(255, 255, 0, 0); + private bool _layerDifferenceHighlightSimilarityInstead = false; + private Color _islandColor = new(255, 255, 255, 0); + private Color _islandHighlightColor = new(255, 255, 215, 0); + private Color _overhangColor = new(255, 255, 105, 180); + private Color _overhangHighlightColor = new(255, 255, 20, 147); + private Color _resinTrapColor = new(255, 255, 165, 0); + private Color _resinTrapHighlightColor = new(255, 244, 164, 96); + private Color _touchingBoundsColor = new(255, 255, 0, 0); + private Color _crosshairColor = new(255, 255, 0, 0); private bool _zoomToFitPrintVolumeBounds = true; private byte _zoomLockLevelIndex = 7; private bool _zoomIssues = true; @@ -406,6 +407,12 @@ namespace UVtools.WPF set => RaiseAndSetIfChanged(ref _showLayerDifference, value); } + public bool LayerDifferenceHighlightSimilarityInstead + { + get => _layerDifferenceHighlightSimilarityInstead; + set => RaiseAndSetIfChanged(ref _layerDifferenceHighlightSimilarityInstead, value); + } + public Color IslandColor { get => _islandColor; diff --git a/UVtools.WPF/Windows/AboutWindow.axaml b/UVtools.WPF/Windows/AboutWindow.axaml index fe270f7..342c762 100644 --- a/UVtools.WPF/Windows/AboutWindow.axaml +++ b/UVtools.WPF/Windows/AboutWindow.axaml @@ -93,7 +93,7 @@ <Border Background="WhiteSmoke"> <Button Command="{Binding Close}" - HotKey="Escape" + IsCancel="True" Padding="10" Margin="20" HorizontalAlignment="Right"> diff --git a/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs b/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs index a519d34..925574d 100644 --- a/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs +++ b/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs @@ -3,7 +3,6 @@ using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using DynamicData; using MessageBox.Avalonia.Enums; using UVtools.Core.Managers; using UVtools.Core.Objects; @@ -83,7 +82,7 @@ namespace UVtools.WPF.Windows { if (_grid.SelectedItems.Count <= 0) return; if (await this.MessageBoxQuestion($"Are you sure you want to remove {_grid.SelectedItems.Count} materials?") != ButtonResult.Yes) return; - Manager.RemoveMany(_grid.SelectedItems.Cast<Material>()); + Manager.RemoveRange(_grid.SelectedItems.Cast<Material>()); MaterialManager.Save(); } diff --git a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml index 72184b1..cb8880c 100644 --- a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml +++ b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml @@ -110,6 +110,7 @@ Orientation="Horizontal" Spacing="5"> <Button Padding="10" + IsDefault="True" Command="{Binding InstallProfiles}" > <StackPanel Orientation="Horizontal" Spacing="10"> @@ -118,7 +119,9 @@ </StackPanel> </Button> - <Button Padding="10" Command="{Binding Close}"> + <Button Padding="10" + IsCancel="True" + Command="{Binding Close}"> <StackPanel Orientation="Horizontal" Spacing="10"> <Image Source="/Assets/Icons/exit-16x16.png"/> <TextBlock Text="Close"/> diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 16c1ab1..a300e3f 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -538,7 +538,7 @@ <!--Diff & Touching bounds--> <CheckBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="3" - Content="Show layer diff colors by default" + Content="Show layer difference by default" IsChecked="{Binding Settings.LayerPreview.ShowLayerDifference}" /> @@ -555,8 +555,13 @@ VerticalAlignment="Center" Background="{Binding Settings.LayerPreview.TouchingBoundsBrush}" Command="{Binding SelectColor}" - CommandParameter="TouchingBoundsColor" - /> + CommandParameter="TouchingBoundsColor"/> + + <CheckBox Grid.Row="8" Grid.Column="0" + Grid.ColumnSpan="3" + Content="Show layer similarity instead of difference" + ToolTip.Tip="If enabled, it will recolor the current layer pixels in common with the previous and next layer" + IsChecked="{Binding Settings.LayerPreview.LayerDifferenceHighlightSimilarityInstead}"/> <!-- Crosshair --> <TextBlock Grid.Row="8" Grid.Column="2" diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml index ac5caa9..1c8b006 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml +++ b/UVtools.WPF/Windows/ToolWindow.axaml @@ -404,8 +404,7 @@ <Button Padding="10" IsCancel="True" - Command="{Binding Close}" - HotKey="Escape"> + Command="{Binding Close}"> <StackPanel Orientation="Horizontal" Spacing="10"> diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs index f0db238..eba5086 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml.cs +++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using Avalonia; @@ -7,7 +6,6 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Threading; -using DynamicData; using MessageBox.Avalonia.Enums; using UVtools.Core; using UVtools.Core.Extensions; @@ -44,7 +42,7 @@ namespace UVtools.WPF.Windows private bool _clearRoiAndMaskAfterOperation; private bool _isProfilesVisible; - private ObservableCollection<Operation> _profiles = new(); + private RangeObservableCollection<Operation> _profiles = new(); private Operation _selectedProfileItem; private string _profileText; @@ -325,7 +323,7 @@ namespace UVtools.WPF.Windows set => RaiseAndSetIfChanged(ref _isProfilesVisible, value); } - public ObservableCollection<Operation> Profiles + public RangeObservableCollection<Operation> Profiles { get => _profiles; set => RaiseAndSetIfChanged(ref _profiles, value); |