Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2021-04-30 20:20:58 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2021-04-30 20:20:58 +0300
commit6d6be66ced23339f8d30f4aed2d32b83d14384af (patch)
tree80ba0c9891eabda762fc896f7d9fa74972535f07
parent6cfedea4cc36200caab87301e0dd121e97ddc8f2 (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
-rw-r--r--CHANGELOG.md22
-rw-r--r--PrusaSlicer/printer/Nova3D Bene4 Mono.ini10
-rw-r--r--UVtools.Core/Extensions/PathExtensions.cs8
-rw-r--r--UVtools.Core/Extensions/StreamExtensions.cs8
-rw-r--r--UVtools.Core/Extensions/StringExtensions.cs4
-rw-r--r--UVtools.Core/Extensions/ZipArchiveExtensions.cs144
-rw-r--r--UVtools.Core/FileFormats/SL1File.cs4
-rw-r--r--UVtools.Core/Managers/ClipboardManager.cs2
-rw-r--r--UVtools.Core/Managers/MaterialManager.cs17
-rw-r--r--UVtools.Core/Objects/RangeObservableCollection.cs703
-rw-r--r--UVtools.Core/Operations/OperationCalibrateExposureFinder.cs13
-rw-r--r--UVtools.Core/Operations/OperationDynamicLayerHeight.cs10
-rw-r--r--UVtools.Core/Operations/OperationLayerImport.cs45
-rw-r--r--UVtools.Core/UVtools.Core.csproj6
-rw-r--r--UVtools.InstallerMM/UVtools.InstallerMM.wxs15
-rw-r--r--UVtools.WPF/App.axaml.cs3
-rw-r--r--UVtools.WPF/Assets/Styles/Styles.xaml11
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs4
-rw-r--r--UVtools.WPF/Controls/KernelControl.axaml.cs4
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs7
-rw-r--r--UVtools.WPF/MainWindow.Information.cs6
-rw-r--r--UVtools.WPF/MainWindow.Issues.cs32
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs107
-rw-r--r--UVtools.WPF/MainWindow.Log.cs2
-rw-r--r--UVtools.WPF/MainWindow.PixelEditor.cs50
-rw-r--r--UVtools.WPF/MainWindow.axaml17
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs2
-rw-r--r--UVtools.WPF/Structures/PEProfileFolder.cs4
-rw-r--r--UVtools.WPF/Structures/PixelPicker.cs1
-rw-r--r--UVtools.WPF/Structures/SlicerProperty.cs15
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj15
-rw-r--r--UVtools.WPF/UserSettings.cs39
-rw-r--r--UVtools.WPF/Windows/AboutWindow.axaml2
-rw-r--r--UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs3
-rw-r--r--UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml5
-rw-r--r--UVtools.WPF/Windows/SettingsWindow.axaml11
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml3
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml.cs6
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&lt;T&gt;,
+ /// 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&lt;T&gt; 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);