diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa94ac5..37fa92c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 07/11/2020 - v1.2.0
+
+* (Add) RAM usage on title bar
+* (Add) Clipboard manager: Undo (Ctrl + Z) and Redo (Ctrl + Y) modifications (Memory optimized)
+* (Add) Current layer properties on information tab
+* (Fix) Long windows with system zoom bigger than 100% were being hidden and overflow (#90)
+* (Fix) Do not recompute issues nor properties nor reshow layer if operation is cancelled or failed
+
## 05/11/2020 - v1.1.3
* (Add) Auto-updater: When a new version is detected UVtools still show the same green button at top,
diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs
new file mode 100644
index 0000000..8e164ec
--- /dev/null
+++ b/UVtools.Core/Extensions/SizeExtensions.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace UVtools.Core.Extensions
+{
+ public static class SizeExtensions
+ {
+ public static readonly string[] SizeSuffixes =
+ { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
+
+ public static string SizeSuffix(long value, byte decimalPlaces = 2)
+ {
+ //if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
+ if (value < 0) { return "-" + SizeSuffix(-value); }
+ if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }
+
+ // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
+ int mag = (int)Math.Log(value, 1024);
+
+ // 1L << (mag * 10) == 2 ^ (10 * mag)
+ // [i.e. the number of bytes in the unit corresponding to mag]
+ decimal adjustedSize = (decimal)value / (1L << (mag * 10));
+
+ // make adjustment when the value is large enough that
+ // it would round up to 1000 or more
+ if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
+ {
+ mag += 1;
+ adjustedSize /= 1024;
+ }
+
+ return string.Format("{0:n" + decimalPlaces + "} {1}",
+ adjustedSize,
+ SizeSuffixes[mag]);
+ }
+ }
+}
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 3ec0f65..fe0f667 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -353,9 +353,23 @@ namespace UVtools.Core.FileFormats
public abstract Size[] ThumbnailsOriginalSize { get; }
public Mat[] Thumbnails { get; set; }
- public LayerManager LayerManager { get; set; }
+
+ public LayerManager LayerManager
+ {
+ get => _layerManager;
+ set
+ {
+ var oldLayerManager = _layerManager;
+ if (!RaiseAndSetIfChanged(ref _layerManager, value) || oldLayerManager is null || value is null) return;
+ if (oldLayerManager.Count != LayerCount)
+ {
+ LayerCount = _layerManager.Count;
+ }
+ }
+ }
private bool _haveModifiedLayers;
+ private LayerManager _layerManager;
///
/// Gets or sets if modifications require a full encode to save
@@ -407,7 +421,7 @@ namespace UVtools.Core.FileFormats
public virtual uint LayerCount
{
get => LayerManager?.Count ?? 0;
- set => throw new NotImplementedException();
+ set { }
}
public virtual ushort BottomLayerCount { get; set; }
@@ -469,8 +483,9 @@ namespace UVtools.Core.FileFormats
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine(e.PropertyName);
- if (e.PropertyName == nameof(LayerCount))
+ if (e.PropertyName == nameof(LayerCount))
{
+ if (this[LayerCount - 1] is null) return; // Not initialized
LayerManager.RebuildLayersProperties();
RebuildGCode();
return;
diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs
index e60d856..39f5551 100644
--- a/UVtools.Core/Layer/Layer.cs
+++ b/UVtools.Core/Layer/Layer.cs
@@ -287,16 +287,19 @@ namespace UVtools.Core
public bool Equals(Layer other)
{
- if (ReferenceEquals(null, other)) return false;
+ if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
- return Equals(_compressedBytes, other._compressedBytes);
+ if (_index != other._index) return false;
+ if (_compressedBytes.Length != other._compressedBytes.Length) return false;
+ return _compressedBytes.AsSpan().SequenceEqual(other._compressedBytes.AsSpan());
+ //return Equals(_compressedBytes, other._compressedBytes);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
+ if (obj.GetType() != GetType()) return false;
return Equals((Layer)obj);
}
diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs
index f6930ea..06f2a72 100644
--- a/UVtools.Core/Layer/LayerManager.cs
+++ b/UVtools.Core/Layer/LayerManager.cs
@@ -237,13 +237,29 @@ namespace UVtools.Core
///
/// Layer index
/// Layer to add
- public void AddLayer(uint index, Layer layer)
+ /// True to add a clone of the layer
+ public void AddLayer(uint index, Layer layer, bool makeClone = false)
{
//layer.Index = index;
- Layers[index] = layer;
+ Layers[index] = makeClone ? layer.Clone() : layer;
layer.ParentLayerManager = this;
}
+ ///
+ /// Add a list of layers
+ ///
+ /// Layers to add
+ /// True to add a clone of layers
+ public void AddLayers(IEnumerable layers, bool makeClone = false)
+ {
+ //layer.Index = index;
+ foreach (var layer in layers)
+ {
+ layer.ParentLayerManager = this;
+ Layers[layer.Index] = makeClone ? layer.Clone() : layer;
+ }
+ }
+
///
/// Get layer given index
///
@@ -2024,6 +2040,24 @@ namespace UVtools.Core
}
}
+ ///
+ /// Reallocate with new size
+ ///
+ ///
+ public LayerManager Reallocate(uint newLayerCount, bool makeClone = false)
+ {
+ LayerManager layerManager = new LayerManager(newLayerCount, SlicerFile);
+ foreach (var layer in this)
+ {
+ if (layer.Index >= newLayerCount) break;
+ layerManager[layer.Index] = makeClone ? layer.Clone() : layer;
+ }
+
+ layerManager.BoundingRectangle = Rectangle.Empty;
+
+ return layerManager;
+ }
+
///
/// Clone this object
///
@@ -2035,9 +2069,8 @@ namespace UVtools.Core
{
layerManager[layer.Index] = layer.Clone();
}
-
layerManager.BoundingRectangle = BoundingRectangle;
-
+
return layerManager;
}
diff --git a/UVtools.Core/Managers/ClipboardManager.cs b/UVtools.Core/Managers/ClipboardManager.cs
new file mode 100644
index 0000000..0273217
--- /dev/null
+++ b/UVtools.Core/Managers/ClipboardManager.cs
@@ -0,0 +1,359 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Drawing;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Objects;
+
+namespace UVtools.Core.Managers
+{
+ public sealed class ClipboardItem : IList
+ {
+ #region Properties
+ private readonly List _layers = new List();
+
+ ///
+ /// Gets the LayerCount for this clip
+ ///
+ public uint LayerCount { get; }
+
+ public float LayerHeight { get; }
+
+ ///
+ /// Gets the description of this operation
+ ///
+ public string Description { get; set; }
+
+ #endregion
+
+ #region Constructor
+ public ClipboardItem(FileFormat slicerFile, string description = null)
+ {
+ LayerCount = slicerFile.LayerCount;
+ LayerHeight = slicerFile.LayerHeight;
+ Description = description;
+ }
+ #endregion
+
+ #region List Implementation
+ public IEnumerator GetEnumerator() => _layers.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public void Add(Layer item) => _layers.Add(item);
+
+ public void Clear() => _layers.Clear();
+
+ public bool Contains(Layer item) => _layers.Contains(item);
+
+ public void CopyTo(Layer[] array, int arrayIndex) => _layers.CopyTo(array, arrayIndex);
+
+ public bool Remove(Layer item) => _layers.Remove(item);
+
+ public int Count => _layers.Count;
+ public bool IsReadOnly => false;
+ public int IndexOf(Layer item) => _layers.IndexOf(item);
+
+ public void Insert(int index, Layer item) => _layers.Insert(index, item);
+
+ public void RemoveAt(int index) => _layers.RemoveAt(index);
+
+ public Layer this[int index]
+ {
+ get => _layers[index];
+ set => _layers[index] = value;
+ }
+ #endregion
+
+ #region Methods
+ public override string ToString()
+ {
+ return $"{Description} ({Count})";
+ }
+ #endregion
+ }
+
+ public sealed class ClipboardManager : BindableBase, IList
+ {
+ #region Properties
+
+ public ObservableCollection Items { get; } = new ObservableCollection();
+
+ public FileFormat SlicerFile { get; set; }
+
+ private int _currentIndex = -1;
+ private LayerManager _snapshotLayerManager;
+ private bool _reallocatedLayerCount;
+ private bool _changedByClip;
+
+ ///
+ /// Gets the index of current item
+ ///
+ public int CurrentIndex {
+ get => _currentIndex;
+ set
+ {
+ if (value >= Count) value = Count-1;
+ if (_currentIndex == value) return;
+ var oldIndex = _currentIndex;
+ _currentIndex = value;
+ //if (!RaiseAndSetIfChanged(ref _currentIndex, value)) return;
+
+ if (value >= 0)
+ {
+ int dir = oldIndex < _currentIndex ? 1 : -1;
+
+ for (int i = oldIndex + dir; i >= 0 && i < Count; i += dir)
+ {
+ var layerManager = SlicerFile.LayerManager;
+ var clip = this[i];
+ if (layerManager.Count != clip.LayerCount) // Need resize layer manager
+ {
+ layerManager = layerManager.Reallocate(clip.LayerCount);
+ ReallocatedLayerCount = true;
+ }
+
+ layerManager.AddLayers(clip);
+
+ layerManager.BoundingRectangle = Rectangle.Empty;
+ if (SlicerFile.LayerHeight != clip.LayerHeight)
+ {
+ SlicerFile.LayerHeight = clip.LayerHeight;
+ }
+ SlicerFile.LayerManager = layerManager.Clone();
+ if (i == _currentIndex) break;
+ }
+ }
+
+ RaisePropertyChanged();
+ RaisePropertyChanged(nameof(CurrentIndexCountStr));
+ RaisePropertyChanged(nameof(CanUndo));
+ RaisePropertyChanged(nameof(CanRedo));
+ return;
+ }
+ }
+
+ public string CurrentIndexCountStr => (CurrentIndex + 1).ToString().PadLeft(Count.ToString().Length, '0');
+
+ public bool ChangedByClip
+ {
+ get => _changedByClip;
+ set => RaiseAndSetIfChanged(ref _changedByClip, value);
+ }
+
+ public bool ReallocatedLayerCount
+ {
+ get => _reallocatedLayerCount;
+ set => RaiseAndSetIfChanged(ref _reallocatedLayerCount, value);
+ }
+
+ public LayerManager SnapshotLayerManager
+ {
+ get => _snapshotLayerManager;
+ private set => RaiseAndSetIfChanged(ref _snapshotLayerManager, value);
+ }
+
+ public bool CanUndo => CurrentIndex < Count - 1;
+ public bool CanRedo => CurrentIndex > 0;
+
+ #endregion
+
+ #region Singleton
+ private static readonly Lazy InstanceHolder =
+ new Lazy(() => new ClipboardManager());
+
+ public static ClipboardManager Instance => InstanceHolder.Value;
+ #endregion
+
+ #region Constructor
+ private ClipboardManager() { }
+ #endregion
+
+ #region List Implementation
+ public IEnumerator GetEnumerator() => Items.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public void Add(ClipboardItem item) => Items.Add(item);
+
+ public void Clear()
+ {
+ Items.Clear();
+ CurrentIndex = -1;
+ }
+
+ public void Clear(bool keepOriginal)
+ {
+ if (!keepOriginal)
+ {
+ Clear();
+ return;
+ }
+
+ if (Count == 0) return;
+
+ Init(SlicerFile);
+ }
+
+ public bool Contains(ClipboardItem item) => Items.Contains(item);
+
+ public void CopyTo(ClipboardItem[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex);
+
+ public bool Remove(ClipboardItem item) => Items.Remove(item);
+
+ public int Count => Items.Count;
+ public bool IsReadOnly => false;
+ public int IndexOf(ClipboardItem item) => Items.IndexOf(item);
+
+ public void Insert(int index, ClipboardItem item) => Items.Insert(index, item);
+
+ public void RemoveAt(int index) => Items.RemoveAt(index);
+
+ public ClipboardItem this[int index]
+ {
+ get => Items[index];
+ set => Items[index] = value;
+ }
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Clears the manager
+ ///
+ public void Reset() => Init(null);
+
+ ///
+ /// Clears and init clipboard
+ ///
+ ///
+ public void Init(FileFormat slicerFile)
+ {
+ Clear();
+ SlicerFile = slicerFile;
+ if(slicerFile is null) return;
+ var clip = new ClipboardItem(SlicerFile, "Original layers");
+ for (int layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++)
+ {
+ clip.Add(SlicerFile[layerIndex].Clone());
+ }
+
+ Add(clip);
+ CurrentIndex = 0;
+ }
+
+ ///
+ /// Snapshot layers and prepare manager to collect modified layers with
+ ///
+ public void Snapshot(LayerManager layerManager = null)
+ {
+ SnapshotLayerManager = layerManager ?? SlicerFile.LayerManager.Clone();
+ }
+
+ ///
+ /// Collect differences and create a clip
+ ///
+ public void Clip(string description = null, LayerManager layerManagerSnapshot = null)
+ {
+ if(!(layerManagerSnapshot is null)) Snapshot(layerManagerSnapshot);
+ if (SnapshotLayerManager is null) throw new InvalidOperationException("A snapshot is required before perform a clip");
+ var clip = new ClipboardItem(SlicerFile, description);
+
+ int layerIndex = 0;
+ for (;
+ layerIndex < SlicerFile.LayerCount
+ && layerIndex < SnapshotLayerManager.Count
+ ; layerIndex++)
+ {
+ //if(SnapshotLayerManager.Count - 1 < layerIndex) break;
+ if(SnapshotLayerManager[layerIndex].Equals(SlicerFile[layerIndex])) continue;
+
+ clip.Add(SlicerFile[layerIndex].Clone());
+ }
+
+ // Collect leftovers from snapshot
+ // This happens when current layer manager has less layers then the snapshot/previous
+ // So we need to preserve them
+ for (;
+ layerIndex < SlicerFile.LayerCount;
+ layerIndex++)
+ {
+ clip.Add(SlicerFile[layerIndex].Clone());
+ }
+
+ SnapshotLayerManager = null;
+
+ if (clip.Count == 0)
+ {
+ if(Count > 0 && this[0].LayerCount == clip.LayerCount)
+ return;
+ }
+
+ ChangedByClip = true;
+ var oldCurrentIndex = _currentIndex;
+ CurrentIndex = -1;
+
+ // Remove all redo's for integrity
+ for (int i = oldCurrentIndex - 1; i >= 0; i--)
+ {
+ RemoveAt(i);
+ }
+
+
+ Insert(0, clip);
+
+ CurrentIndex = 0;
+ ChangedByClip = false;
+ }
+
+ public void Undo()
+ {
+ if (!CanUndo) return;
+ CurrentIndex++;
+ }
+
+ public void Redo()
+ {
+ if (!CanRedo) return;
+ CurrentIndex--;
+ }
+
+ public void SetToOldest() => CurrentIndex = Count-1;
+ public void SetToNewest() => CurrentIndex = 0;
+
+ /*public bool SetTo(int index)
+ {
+ if (index == _currentIndex || index < 0 || index >= Count) return false;
+ bool changed = false;
+ int dir = _currentIndex < index ? 1 : -1;
+
+ for (int i = _currentIndex+dir; i >= 0 && i < Count; i+=dir)
+ {
+ var layerManager = SlicerFile.LayerManager;
+ var clip = this[i];
+ if (layerManager.Count != clip.LayerCount) // Need resize layer manager
+ {
+ layerManager = layerManager.Reallocate(clip.LayerCount);
+ ReallocatedLayerCount = true;
+ }
+
+ layerManager.AddLayers(clip);
+
+ layerManager.BoundingRectangle = Rectangle.Empty;
+ SlicerFile.LayerManager = layerManager.Clone();
+ changed = true;
+ if (i == index) break;
+ }
+
+ if (changed)
+ {
+ CurrentIndex = index;
+ }
+
+ return changed;
+ }*/
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Objects/StringTag.cs b/UVtools.Core/Objects/StringTag.cs
index 0e22913..d19d686 100644
--- a/UVtools.Core/Objects/StringTag.cs
+++ b/UVtools.Core/Objects/StringTag.cs
@@ -11,12 +11,28 @@ using System.Collections.Generic;
namespace UVtools.Core.Objects
{
- public class StringTag : IComparable
+ public class StringTag : BindableBase, IEquatable, IComparable
{
- public string Content { get; set; }
+ private string _content;
+ private object _tag;
- public object Tag { get; set; }
- public string TagString => Tag.ToString();
+ public string Content
+ {
+ get => _content;
+ set => RaiseAndSetIfChanged(ref _content, value);
+ }
+
+ public object Tag
+ {
+ get => _tag;
+ set => RaiseAndSetIfChanged(ref _tag, value);
+ }
+
+ public string TagString
+ {
+ get => Tag.ToString();
+ set => Tag = value;
+ }
public StringTag(object content, object tag = null)
{
@@ -29,6 +45,27 @@ namespace UVtools.Core.Objects
Tag = tag;
}
+ public bool Equals(StringTag other)
+ {
+ return _content == other._content && Equals(_tag, other._tag);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((StringTag) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((_content != null ? _content.GetHashCode() : 0) * 397) ^ (_tag != null ? _tag.GetHashCode() : 0);
+ }
+ }
+
private sealed class ContentEqualityComparer : IEqualityComparer
{
public bool Equals(StringTag x, StringTag y)
diff --git a/UVtools.Core/Operations/OperationLayerClone.cs b/UVtools.Core/Operations/OperationLayerClone.cs
index 2271135..eb77b6c 100644
--- a/UVtools.Core/Operations/OperationLayerClone.cs
+++ b/UVtools.Core/Operations/OperationLayerClone.cs
@@ -64,5 +64,12 @@ namespace UVtools.Core.Operations
}
#endregion
+
+ public override string ToString()
+ {
+ var result = $"[Clones: {Clones}]" + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
}
}
diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs
index df45ad4..4a22279 100644
--- a/UVtools.Core/Operations/OperationLayerImport.cs
+++ b/UVtools.Core/Operations/OperationLayerImport.cs
@@ -178,7 +178,12 @@ namespace UVtools.Core.Operations
return (uint)(totalLayers + Files.Count - (ReplaceStartLayer ? 1 : 0));
}
-
+ public override string ToString()
+ {
+ var result = $"[Files: {Count}]" + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
#endregion
}
diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs
index 32bf0bc..f92c7ce 100644
--- a/UVtools.Core/Operations/OperationLayerReHeight.cs
+++ b/UVtools.Core/Operations/OperationLayerReHeight.cs
@@ -119,5 +119,12 @@ namespace UVtools.Core.Operations
return (IsMultiply ? 'x' : '÷') + $" {Modifier} → {LayerCount} layers at {LayerHeight}mm";
}
}
+
+ public override string ToString()
+ {
+ var result = $"[Layer Count: {Item.LayerCount}] [Layer Height: {Item.LayerHeight}]" + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
}
}
diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs
index b25b611..64956bd 100644
--- a/UVtools.Core/Operations/OperationMove.cs
+++ b/UVtools.Core/Operations/OperationMove.cs
@@ -236,5 +236,12 @@ namespace UVtools.Core.Operations
CalculateDstRoi();
return IsWithinBoundary;
}
+
+ public override string ToString()
+ {
+ var result = $"[{ROI} -> {DstRoi}] [Cut: {IsCutMove}]" + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
}
}
diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs
index 7d66f7d..fcf8f4e 100644
--- a/UVtools.Core/Operations/OperationPattern.cs
+++ b/UVtools.Core/Operations/OperationPattern.cs
@@ -254,5 +254,12 @@ namespace UVtools.Core.Operations
var volume = GetPatternVolume;
return IsWithinBoundary = volume.Width <= ImageWidth && volume.Height <= ImageHeight;
}
+
+ public override string ToString()
+ {
+ var result = $"[Rows: {Rows}] [Cols: {Cols}]" + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
}
}
diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs
index d54054e..2622432 100644
--- a/UVtools.Core/Operations/OperationRepairLayers.cs
+++ b/UVtools.Core/Operations/OperationRepairLayers.cs
@@ -96,6 +96,5 @@ namespace UVtools.Core.Operations
[XmlIgnore]
public List Issues { get; set; }
-
}
}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 1966dac..b4214c1 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
https://github.com/sn4k3/UVtools
https://github.com/sn4k3/UVtools
MSLA/DLP, file analysis, repair, conversion and manipulation
- 1.1.3
+ 1.2.0
Copyright © 2020 PTRTECH
UVtools.png
AnyCPU;x64
diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs
index 8df3fa8..d628c8c 100644
--- a/UVtools.WPF/App.axaml.cs
+++ b/UVtools.WPF/App.axaml.cs
@@ -10,10 +10,12 @@ using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia;
+using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
@@ -174,7 +176,7 @@ namespace UVtools.WPF
var assemblyName = Assembly.GetEntryAssembly().GetName().Name;
uri = new Uri($"avares://{assemblyName}{url}");
}
-
+
var res = AvaloniaLocator.Current.GetService().Open(uri);
return res;
}
@@ -205,5 +207,86 @@ namespace UVtools.WPF
}
#endregion
+
+ #region Assembly Attribute Accessors
+
+ public static Version Version => Assembly.GetExecutingAssembly().GetName().Version;
+ public static string VersionStr => Version.ToString(3);
+
+ public static string AssemblyTitle
+ {
+ get
+ {
+ object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
+ if (attributes.Length > 0)
+ {
+ AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
+ if (titleAttribute.Title != "")
+ {
+ return titleAttribute.Title;
+ }
+ }
+ return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase);
+ }
+ }
+
+ public static string AssemblyVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
+
+ public static string AssemblyDescription
+ {
+ get
+ {
+ object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
+ if (attributes.Length == 0)
+ {
+ return "";
+ }
+
+ string description = ((AssemblyDescriptionAttribute)attributes[0]).Description + $"{Environment.NewLine}{Environment.NewLine}Available File Formats:";
+
+ return FileFormat.AvaliableFormats.SelectMany(fileFormat => fileFormat.FileExtensions).Aggregate(description, (current, fileExtension) => current + $"{Environment.NewLine}- {fileExtension.Description} (.{fileExtension.Extension})");
+ }
+ }
+
+ public static string AssemblyProduct
+ {
+ get
+ {
+ object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);
+ if (attributes.Length == 0)
+ {
+ return "";
+ }
+ return ((AssemblyProductAttribute)attributes[0]).Product;
+ }
+ }
+
+ public static string AssemblyCopyright
+ {
+ get
+ {
+ object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
+ if (attributes.Length == 0)
+ {
+ return "";
+ }
+ return ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
+ }
+ }
+
+ public static string AssemblyCompany
+ {
+ get
+ {
+ object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
+ if (attributes.Length == 0)
+ {
+ return "";
+ }
+ return ((AssemblyCompanyAttribute)attributes[0]).Company;
+ }
+ }
+
+ #endregion
}
}
diff --git a/UVtools.WPF/AppSettings.cs b/UVtools.WPF/AppSettings.cs
index e33b085..7ea39c9 100644
--- a/UVtools.WPF/AppSettings.cs
+++ b/UVtools.WPF/AppSettings.cs
@@ -14,8 +14,6 @@ namespace UVtools.WPF
{
public static class AppSettings
{
- public static Version Version => Assembly.GetEntryAssembly().GetName().Version;
- public static string VersionStr => Version.ToString(3);
// Supported ZoomLevels for Layer Preview.
// These settings eliminate very small zoom factors from the ImageBox default values,
// while ensuring that 4K/5K build plates can still easily fit on screen.
@@ -43,82 +41,6 @@ namespace UVtools.WPF
///
public const byte MinLockedZoomLevel = 200;
- #region Assembly Attribute Accessors
-
- public static string AssemblyTitle
- {
- get
- {
- object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
- if (attributes.Length > 0)
- {
- AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
- if (titleAttribute.Title != "")
- {
- return titleAttribute.Title;
- }
- }
- return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase);
- }
- }
-
- public static string AssemblyVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
-
- public static string AssemblyDescription
- {
- get
- {
- object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
- if (attributes.Length == 0)
- {
- return "";
- }
-
- string description = ((AssemblyDescriptionAttribute)attributes[0]).Description + $"{Environment.NewLine}{Environment.NewLine}Available File Formats:";
-
- return FileFormat.AvaliableFormats.SelectMany(fileFormat => fileFormat.FileExtensions).Aggregate(description, (current, fileExtension) => current + $"{Environment.NewLine}- {fileExtension.Description} (.{fileExtension.Extension})");
- }
- }
-
- public static string AssemblyProduct
- {
- get
- {
- object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);
- if (attributes.Length == 0)
- {
- return "";
- }
- return ((AssemblyProductAttribute)attributes[0]).Product;
- }
- }
-
- public static string AssemblyCopyright
- {
- get
- {
- object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
- if (attributes.Length == 0)
- {
- return "";
- }
- return ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
- }
- }
-
- public static string AssemblyCompany
- {
- get
- {
- object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
- if (attributes.Length == 0)
- {
- return "";
- }
- return ((AssemblyCompanyAttribute)attributes[0]).Company;
- }
- }
-
- #endregion
+
}
}
diff --git a/UVtools.WPF/Assets/Icons/clipboard-32x32.png b/UVtools.WPF/Assets/Icons/clipboard-32x32.png
new file mode 100644
index 0000000..e4d7c2b
Binary files /dev/null and b/UVtools.WPF/Assets/Icons/clipboard-32x32.png differ
diff --git a/UVtools.WPF/Assets/Icons/layers-alt-32x32.png b/UVtools.WPF/Assets/Icons/layers-alt-32x32.png
new file mode 100644
index 0000000..b1ed164
Binary files /dev/null and b/UVtools.WPF/Assets/Icons/layers-alt-32x32.png differ
diff --git a/UVtools.WPF/Assets/Icons/redo-16x16.png b/UVtools.WPF/Assets/Icons/redo-16x16.png
new file mode 100644
index 0000000..3408d68
Binary files /dev/null and b/UVtools.WPF/Assets/Icons/redo-16x16.png differ
diff --git a/UVtools.WPF/MainWindow.Clipboard.cs b/UVtools.WPF/MainWindow.Clipboard.cs
new file mode 100644
index 0000000..e69c912
--- /dev/null
+++ b/UVtools.WPF/MainWindow.Clipboard.cs
@@ -0,0 +1,57 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc.
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+
+using System;
+using System.ComponentModel;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using MessageBox.Avalonia.Enums;
+using UVtools.WPF.Extensions;
+
+namespace UVtools.WPF
+{
+ public partial class MainWindow
+ {
+ public ListBox ClipboardList;
+
+ public void InitClipboardLayers()
+ {
+ ClipboardList = this.FindControl(nameof(ClipboardList));
+ Clipboard.PropertyChanged += ClipboardOnPropertyChanged;
+ }
+
+ private void ClipboardOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Clipboard.CurrentIndex))
+ {
+ if (Clipboard.CurrentIndex < 0 || Clipboard.ChangedByClip) return;
+
+ if (Clipboard.ReallocatedLayerCount)
+ {
+ DispatcherTimer.RunOnce(() =>
+ {
+ RefreshProperties();
+ ResetDataContext();
+ }, TimeSpan.FromMilliseconds(1));
+ }
+
+ ShowLayer();
+ return;
+ }
+ }
+
+ public async void ClipboardClear()
+ {
+ if (await this.MessageBoxQuestion("Are you sure you want to clear the clipboard?\n" +
+ "Current layers will be placed as original layers\n" +
+ "This action is permanent!", "Clear clipboard?") != ButtonResult.Yes) return;
+ Clipboard.Clear(true);
+ }
+ }
+}
diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs
index 92437c3..f5ee13d 100644
--- a/UVtools.WPF/MainWindow.Information.cs
+++ b/UVtools.WPF/MainWindow.Information.cs
@@ -7,7 +7,9 @@
*/
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -16,6 +18,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using MessageBox.Avalonia.Enums;
+using UVtools.Core.Objects;
using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
using Bitmap = Avalonia.Media.Imaging.Bitmap;
@@ -27,32 +30,80 @@ namespace UVtools.WPF
{
public ObservableCollection SlicerProperties { get; } = new ObservableCollection();
public DataGrid PropertiesGrid;
+ public DataGrid CurrentLayerGrid;
private uint _visibleThumbnailIndex;
private Bitmap _visibleThumbnailImage;
+ private ObservableCollection _currentLayerProperties = new ObservableCollection();
+
+ public ObservableCollection CurrentLayerProperties
+ {
+ get => _currentLayerProperties;
+ set => RaiseAndSetIfChanged(ref _currentLayerProperties, value);
+ }
public void InitInformation()
{
- PropertiesGrid = this.Find("PropertiesGrid");
- PropertiesGrid.KeyUp += PropertiesGridOnKeyUp;
+ PropertiesGrid = this.Find(nameof(PropertiesGrid));
+ CurrentLayerGrid = this.Find(nameof(CurrentLayerGrid));
+ PropertiesGrid.KeyUp += GridOnKeyUp;
+ CurrentLayerGrid.KeyUp += GridOnKeyUp;
+ /*CurrentLayerGrid.BeginningEdit += (sender, e) =>
+ {
+ if (e.Row.DataContext is StringTag stringTag)
+ {
+ if (e.Column.DisplayIndex == 0
+ || e.Row.DataContext.ToString() != nameof(LayerCache.Layer.ExposureTime)
+ && e.Row.DataContext.ToString() != nameof(LayerCache.Layer.LightPWM)
+ )
+ {
+ e.Cancel = true;
+ }
+ }
+ else
+ {
+ e.Cancel = true;
+ }
+ };
+ CurrentLayerGrid.RowEditEnding += (sender, e) =>
+ {
+ if (e.EditAction == DataGridEditAction.Cancel) return;
+ if (!(e.Row.DataContext is StringTag stringTag)) return;
+ if (float.TryParse(stringTag.TagString, out var result)) return;
+ e.Cancel = true;
+ };
+ CurrentLayerGrid.RowEditEnded += (sender, e) =>
+ {
+ if (e.EditAction == DataGridEditAction.Cancel) return;
+ if (!(e.Row.DataContext is StringTag stringTag)) return;
+ switch (stringTag.Content)
+ {
+ //case nameof(LayerCache.)
+ }
+ };*/
+
}
- private void PropertiesGridOnKeyUp(object? sender, KeyEventArgs e)
+ private void GridOnKeyUp(object? sender, KeyEventArgs e)
{
- switch (e.Key)
+ if (sender is DataGrid dataGrid)
{
- case Key.Escape:
- PropertiesGrid.SelectedItems.Clear();
- break;
- case Key.Multiply:
- var selectedItems = PropertiesGrid.SelectedItems.OfType().ToList();
- PropertiesGrid.SelectedItems.Clear();
- foreach (SlicerProperty item in SlicerProperties)
- {
- if (!selectedItems.Contains(item))
- PropertiesGrid.SelectedItems.Add(item);
- }
- break;
+ switch (e.Key)
+ {
+ case Key.Escape:
+ dataGrid.SelectedItems.Clear();
+ break;
+ case Key.Multiply:
+ foreach (var item in dataGrid.Items)
+ {
+ if (dataGrid.SelectedItems.Contains(item))
+ dataGrid.SelectedItems.Remove(item);
+ else
+ dataGrid.SelectedItems.Add(item);
+ }
+
+ break;
+ }
}
}
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index fca4b2b..d2b3872 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -26,6 +27,7 @@ using Emgu.CV.Structure;
using Emgu.CV.Util;
using UVtools.Core;
using UVtools.Core.Extensions;
+using UVtools.Core.Objects;
using UVtools.Core.PixelEditor;
using UVtools.WPF.Controls;
using UVtools.WPF.Extensions;
@@ -338,15 +340,31 @@ namespace UVtools.WPF
if (!RaiseAndSetIfChanged(ref _actualLayer, value)) return;
ShowLayer();
InvalidateLayerNavigation();
+ var layer = LayerCache.Layer;
+ CurrentLayerProperties.Clear();
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.Index), $"{layer.Index}"));
+ //CurrentLayerProperties.Add(new KeyValuePair(nameof(layer.Filename), layer.Filename));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.PositionZ), $"{layer.PositionZ.ToString(CultureInfo.InvariantCulture)}mm"));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.IsBottomLayer), layer.IsBottomLayer.ToString()));
+ //CurrentLayerProperties.Add(new StringTag(nameof(layer.BoundingRectangle), layer.BoundingRectangle.ToString()));
+ //CurrentLayerProperties.Add(new StringTag(nameof(layer.NonZeroPixelCount), layer.NonZeroPixelCount.ToString()));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.ExposureTime), $"{layer.ExposureTime.ToString(CultureInfo.InvariantCulture)}s"));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.LiftHeight), $"{layer.LiftHeight.ToString(CultureInfo.InvariantCulture)}mm @ {layer.LiftSpeed.ToString(CultureInfo.InvariantCulture)}mm/min"));
+ //CurrentLayerProperties.Adnew StringTagg>(nameof(layer.LiftSpeed), $"{layer.LiftSpeed.ToString(CultureInfo.InvariantCulture)}mm/min"));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.RetractSpeed), $"{layer.RetractSpeed.ToString(CultureInfo.InvariantCulture)}mm/min"));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.LayerOffTime), $"{layer.LayerOffTime.ToString(CultureInfo.InvariantCulture)}s"));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.LightPWM), layer.LightPWM.ToString()));
}
}
public void ForceUpdateActualLayer(uint layerIndex = 0)
{
- _actualLayer = layerIndex;
- ShowLayer();
+ //_actualLayer = layerIndex;
+ /*ShowLayer();
InvalidateLayerNavigation();
- RaisePropertyChanged(nameof(ActualLayer));
+ RaisePropertyChanged(nameof(ActualLayer));*/
+ _actualLayer = uint.MaxValue;
+ ActualLayer = layerIndex;
}
public void InvalidateLayerNavigation()
diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs
index 93841da..2800516 100644
--- a/UVtools.WPF/MainWindow.PixelEditor.cs
+++ b/UVtools.WPF/MainWindow.PixelEditor.cs
@@ -387,6 +387,8 @@ namespace UVtools.WPF
{
IsGUIEnabled = false;
+ Clipboard.Snapshot();
+
var task = await Task.Factory.StartNew(async () =>
{
ShowProgressWindow("Drawing pixels");
@@ -411,6 +413,7 @@ namespace UVtools.WPF
IsGUIEnabled = true;
+ Clipboard.Clip($"Draw {Drawings.Count} modifications");
if (Settings.PixelEditor.PartialUpdateIslandsOnEditing)
{
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 8492e14..5fd1b26 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -312,7 +312,7 @@
+ RowDefinitions="Auto,Auto,Auto,*,Auto,Auto">
@@ -462,7 +462,36 @@
Width="Auto" />
+
+
+
+
+
+
+
+
+
+
@@ -1239,30 +1268,86 @@
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
- !-->
+
App.VersionChecker;
public UserSettings Settings => UserSettings.Instance;
public FileFormat SlicerFile => App.SlicerFile;
+ public ClipboardManager Clipboard => ClipboardManager.Instance;
#endregion
#region Controls
@@ -347,6 +349,7 @@ namespace UVtools.WPF
InitInformation();
InitIssues();
InitPixelEditor();
+ InitClipboardLayers();
InitLayerPreview();
@@ -383,12 +386,11 @@ namespace UVtools.WPF
var windowStateObs = this.GetObservable(WindowStateProperty);
windowStateObs.Subscribe(size => UpdateLayerTrackerHighlightIssues());
-
UpdateTitle();
if (Settings.General.StartMaximized
- || ClientSize.Width > Screens.Primary.Bounds.Width
- || ClientSize.Height > Screens.Primary.Bounds.Height)
+ || ClientSize.Width > Screens.Primary.Bounds.Width / Screens.Primary.PixelDensity
+ || ClientSize.Height > Screens.Primary.Bounds.Height / Screens.Primary.PixelDensity)
{
WindowState = WindowState.Maximized;
}
@@ -417,6 +419,12 @@ namespace UVtools.WPF
{
ProcessFile(About.DemoFile);
}
+
+ DispatcherTimer.Run(() =>
+ {
+ UpdateTitle();
+ return true;
+ }, TimeSpan.FromSeconds(1));
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
@@ -612,6 +620,9 @@ namespace UVtools.WPF
public void CloseFile()
{
if (SlicerFile is null) return;
+
+ ClipboardManager.Instance.Reset();
+
SlicerFile?.Dispose();
App.SlicerFile = null;
@@ -692,7 +703,7 @@ namespace UVtools.WPF
{
var result =
await this.MessageBoxQuestion(
- $"Do you like to auto-update {About.Software} v{AppSettings.Version} to v{VersionChecker.Version}?\n\n" +
+ $"Do you like to auto-update {About.Software} v{App.Version} to v{VersionChecker.Version}?\n\n" +
"Yes: Auto update\n" +
"No: Manual update\n" +
"Cancel: No action", "Update UVtools?", ButtonEnum.YesNoCancel);
@@ -739,10 +750,12 @@ namespace UVtools.WPF
private void UpdateTitle()
{
Title = (SlicerFile is null
- ? $"{About.Software} Version: {AppSettings.VersionStr}"
- : $"{About.Software} File: {Path.GetFileName(SlicerFile.FileFullPath)} ({Math.Round(LastStopWatch.ElapsedMilliseconds / 1000m, 2)}s) Version: {AppSettings.VersionStr}")
+ ? $"{About.Software} Version: {App.VersionStr}"
+ : $"{About.Software} File: {Path.GetFileName(SlicerFile.FileFullPath)} ({Math.Round(LastStopWatch.ElapsedMilliseconds / 1000m, 2)}s) Version: {App.VersionStr}")
;
+ Title += $" RAM: {SizeExtensions.SizeSuffix(Environment.WorkingSet)}";
+
#if DEBUG
Title += " [DEBUG]";
#endif
@@ -825,6 +838,8 @@ namespace UVtools.WPF
return;
}
+ ClipboardManager.Instance.Init(SlicerFile);
+
if (!(SlicerFile.ConvertToFormats is null))
{
List