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