diff --git a/CHANGELOG.md b/CHANGELOG.md index 21a5ea9..fa94ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 05/11/2020 - v1.1.3 + +* (Add) Auto-updater: When a new version is detected UVtools still show the same green button at top, +on click, it will prompt for auto or manual update. +On Linux and Mac the script will kill all UVtools instances and auto-upgrade. +On Windows the user must close all instances and continue with the shown MSI installation +* (Add) Tool profiles: Create and remove named presets for some tools +* (Add) Event handler for handling non-UI thread exceptions +* (Fix) Mac: File - Open in a new window was not working +* (Fix) Tool - Rotate: Allow negative angles +* (Fix) Tool - Rotate: The operation was inverting the angle +* (Fix) Tools: Select normal layers can crash the program with small files with low layer count, eg: 3 layers total + ## 02/11/2020 - v1.1.2 * (Add) Program start elapsed seconds on Log diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs index 342f119..e60d856 100644 --- a/UVtools.Core/Layer/Layer.cs +++ b/UVtools.Core/Layer/Layer.cs @@ -685,7 +685,7 @@ namespace UVtools.Core var halfHeight = target.Height / 2.0f; using (var translateTransform = new Matrix(2, 3)) { - CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), (double) operation.AngleDegrees, 1.0, translateTransform); + CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), (double) -operation.AngleDegrees, 1.0, translateTransform); /*var rect = new RotatedRect(PointF.Empty, mat.Size, (float) angle).MinAreaRect(); translateTransform[0, 2] += rect.Width / 2.0 - mat.Cols / 2.0; translateTransform[0, 2] += rect.Height / 2.0 - mat.Rows / 2.0;*/ diff --git a/UVtools.Core/Objects/Kernel.cs b/UVtools.Core/Objects/Kernel.cs index f57c93c..b73a0a5 100644 --- a/UVtools.Core/Objects/Kernel.cs +++ b/UVtools.Core/Objects/Kernel.cs @@ -6,11 +6,13 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Drawing; using Emgu.CV; namespace UVtools.Core.Objects { + [Serializable] public sealed class Kernel { public Matrix Matrix { get; set; } diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index b9d374c..385b6a9 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -6,17 +6,22 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Drawing; +using System.Xml.Serialization; using Emgu.CV; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public abstract class Operation : BindableBase { private Rectangle _roi = Rectangle.Empty; private uint _layerIndexEnd; private uint _layerIndexStart; + private string _profileName; + private Enumerations.LayerRangeSelection _layerRangeSelection = Enumerations.LayerRangeSelection.All; public const byte ClassNameLength = 9; /// @@ -24,7 +29,16 @@ namespace UVtools.Core.Operations /// public string Id => GetType().Name.Remove(0, ClassNameLength); - public virtual Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.All; + public virtual Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.All; + + /// + /// Gets the last used layer range selection, returns none if custom + /// + public Enumerations.LayerRangeSelection LayerRangeSelection + { + get => _layerRangeSelection; + set => RaiseAndSetIfChanged(ref _layerRangeSelection, value); + } /// /// Gets if this operation should set layer range to the actual layer index on layer preview @@ -32,9 +46,14 @@ namespace UVtools.Core.Operations public virtual bool PassActualLayerIndex => false; /// - /// Gets if this control can make use of ROI + /// Gets if this operation can make use of ROI /// - public virtual bool CanROI { get; set; } = true; + public virtual bool CanROI => true; + + /// + /// Gets if this operation can store profiles + /// + public virtual bool CanHaveProfiles => true; /// /// Gets if this operation supports cancellation @@ -105,9 +124,19 @@ namespace UVtools.Core.Operations public uint LayerRangeCount => LayerIndexEnd - LayerIndexStart + 1; + /// + /// Gets the name for this profile + /// + public string ProfileName + { + get => _profileName; + set => RaiseAndSetIfChanged(ref _profileName, value); + } + /// /// Gets or sets an ROI to process this operation /// + [XmlIgnore] public Rectangle ROI { get => _roi; @@ -120,5 +149,31 @@ namespace UVtools.Core.Operations { return HaveROI ? new Mat(defaultMat, ROI) : defaultMat; } + + public virtual Operation Clone() + { + return MemberwiseClone() as Operation; + } + + public override string ToString() + { + if (!string.IsNullOrEmpty(ProfileName)) return ProfileName; + + var result = $"{Title}: {LayerRangeString}"; + return result; + } + + public virtual string LayerRangeString + { + get + { + if (LayerRangeSelection == Enumerations.LayerRangeSelection.None) + { + return $" [Layers: {LayerIndexStart}-{LayerIndexEnd}]"; + } + + return $" [Layers: {LayerRangeSelection}]"; + } + } } } diff --git a/UVtools.Core/Operations/OperationArithmetic.cs b/UVtools.Core/Operations/OperationArithmetic.cs index bb55b8a..00f9f61 100644 --- a/UVtools.Core/Operations/OperationArithmetic.cs +++ b/UVtools.Core/Operations/OperationArithmetic.cs @@ -9,11 +9,13 @@ using System; using System.Collections.Generic; using System.Text; +using System.Xml.Serialization; using Emgu.CV; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationArithmetic : Operation { private string _sentence; @@ -85,8 +87,10 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _sentence, value); } + [XmlIgnore] public List Operations { get; } = new List(); + [XmlIgnore] public List SetLayers { get; } = new List(); public bool IsValid => SetLayers.Count > 0 & Operations.Count > 0; @@ -181,9 +185,29 @@ namespace UVtools.Core.Operations return true; } - public OperationArithmetic() + public override string ToString() { - + var result = $"{_sentence}" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + protected bool Equals(OperationArithmetic other) + { + return _sentence == other._sentence; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((OperationArithmetic) obj); + } + + public override int GetHashCode() + { + return (_sentence != null ? _sentence.GetHashCode() : 0); } } } diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs index e3042d1..f8c1e58 100644 --- a/UVtools.Core/Operations/OperationBlur.cs +++ b/UVtools.Core/Operations/OperationBlur.cs @@ -6,11 +6,14 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Text; +using System.Xml.Serialization; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationBlur : Operation { private BlurAlgorithm _blurOperation = BlurAlgorithm.Blur; @@ -80,6 +83,19 @@ namespace UVtools.Core.Operations new StringTag("Filter 2D: Applies an arbitrary linear filter to an image", BlurAlgorithm.Filter2D), }; + public byte BlurTypeIndex + { + get + { + for (byte i = 0; i < BlurTypes.Length; i++) + { + if ((BlurAlgorithm)BlurTypes[i].Tag == BlurOperation) return i; + } + + return 0; + } + } + public BlurAlgorithm BlurOperation { get => _blurOperation; @@ -92,10 +108,36 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _size, value); } + [XmlIgnore] public Kernel Kernel { get; set; } = new Kernel(); + public override string ToString() + { + var result = $"[{_blurOperation}] [Size: {_size}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion - + #region Equality + private bool Equals(OperationBlur other) + { + return _blurOperation == other._blurOperation && _size == other._size; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationBlur other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((int) _blurOperation * 397) ^ (int) _size; + } + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationCalculator.cs b/UVtools.Core/Operations/OperationCalculator.cs index 06f0687..b88274c 100644 --- a/UVtools.Core/Operations/OperationCalculator.cs +++ b/UVtools.Core/Operations/OperationCalculator.cs @@ -12,6 +12,7 @@ using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationCalculator : Operation { public override string Title => "Calculator"; @@ -23,9 +24,11 @@ namespace UVtools.Core.Operations public override string ProgressAction => null; - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.None; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; public override bool CanROI => false; + public override bool CanHaveProfiles => false; + public MillimetersToPixels CalcMillimetersToPixels { get; set; } public LightOffDelayC CalcLightOffDelay { get; set; } diff --git a/UVtools.Core/Operations/OperationChangeResolution.cs b/UVtools.Core/Operations/OperationChangeResolution.cs index 9fa8eb1..da9160c 100644 --- a/UVtools.Core/Operations/OperationChangeResolution.cs +++ b/UVtools.Core/Operations/OperationChangeResolution.cs @@ -13,6 +13,7 @@ using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationChangeResolution : Operation { private uint _newResolutionX; @@ -48,8 +49,8 @@ namespace UVtools.Core.Operations #region Overrides - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.None; - public override bool CanROI { get; set; } = false; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; + public override bool CanROI => false; public override string Title => "Change print resolution"; public override string Description => "Crops or resizes all layer images to fit an alternate print area\n\n" + @@ -145,6 +146,35 @@ namespace UVtools.Core.Operations public static Resolution[] Presets => GetResolutions(); + public override string ToString() + { + var result = $"{_newResolutionX} x {_newResolutionY}"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Equality + + private bool Equals(OperationChangeResolution other) + { + return _newResolutionX == other._newResolutionX && _newResolutionY == other._newResolutionY; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationChangeResolution other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((int) _newResolutionX * 397) ^ (int) _newResolutionY; + } + } + #endregion } diff --git a/UVtools.Core/Operations/OperationEditParameters.cs b/UVtools.Core/Operations/OperationEditParameters.cs index 0c4dd3f..7f774a6 100644 --- a/UVtools.Core/Operations/OperationEditParameters.cs +++ b/UVtools.Core/Operations/OperationEditParameters.cs @@ -6,6 +6,7 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Linq; using System.Text; using UVtools.Core.FileFormats; @@ -13,13 +14,14 @@ using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationEditParameters : Operation { private bool _perLayerOverride; - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.None; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; - public override bool CanROI { get; set; } = false; + public override bool CanROI => false; public override string Title => "Edit print parameters"; @@ -58,6 +60,8 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Changing print parameters"; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); diff --git a/UVtools.Core/Operations/OperationFlip.cs b/UVtools.Core/Operations/OperationFlip.cs index fad6768..aeadc67 100644 --- a/UVtools.Core/Operations/OperationFlip.cs +++ b/UVtools.Core/Operations/OperationFlip.cs @@ -11,8 +11,12 @@ using Emgu.CV.CvEnum; namespace UVtools.Core.Operations { + [Serializable] public class OperationFlip : Operation { + private bool _makeCopy; + private Enumerations.FlipDirection _flipDirection = Enumerations.FlipDirection.Horizontally; + public override string Title => "Flip"; public override string Description => "Flip the layers of the model vertically and/or horizontally."; @@ -29,9 +33,19 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Flipped layers"; - public Enumerations.FlipDirection FlipDirection { get; set; } = Enumerations.FlipDirection.Horizontally; + public Enumerations.FlipDirection FlipDirection + { + get => _flipDirection; + set => RaiseAndSetIfChanged(ref _flipDirection, value); + } - public bool MakeCopy { get; set; } + public bool MakeCopy + { + get => _makeCopy; + set => RaiseAndSetIfChanged(ref _makeCopy, value); + } + + public static Array FlipDirections => Enum.GetValues(typeof(Enumerations.FlipDirection)); public FlipType FlipTypeOpenCV { @@ -55,6 +69,36 @@ namespace UVtools.Core.Operations } } - public static Array FlipDirections => Enum.GetValues(typeof(Enumerations.FlipDirection)); + public override string ToString() + { + var result = $"[{_flipDirection}] [Blend: {_makeCopy}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #region Equality + + protected bool Equals(OperationFlip other) + { + return _makeCopy == other._makeCopy && _flipDirection == other._flipDirection; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((OperationFlip) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (_makeCopy.GetHashCode() * 397) ^ (int) _flipDirection; + } + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationLayerClone.cs b/UVtools.Core/Operations/OperationLayerClone.cs index e1e896c..2271135 100644 --- a/UVtools.Core/Operations/OperationLayerClone.cs +++ b/UVtools.Core/Operations/OperationLayerClone.cs @@ -5,19 +5,22 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; using System.Text; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationLayerClone : Operation { private uint _clones = 1; #region Overrides - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.Current; - public override bool CanROI { get; set; } = false; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Current; + public override bool CanROI => false; public override bool PassActualLayerIndex => true; public override string Title => "Clone layers"; @@ -34,6 +37,8 @@ namespace UVtools.Core.Operations public override bool CanCancel => false; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index 07099f0..df45ad4 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -20,6 +20,7 @@ using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationLayerImport : Operation { private uint _insertAfterLayerIndex; @@ -31,8 +32,8 @@ namespace UVtools.Core.Operations #region Overrides - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.None; - public override bool CanROI { get; set; } = false; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; + public override bool CanROI => false; public override string Title => "Import Layers"; public override string Description => @@ -51,6 +52,8 @@ namespace UVtools.Core.Operations public override uint LayerIndexStart => InsertAfterLayerIndex + (ReplaceStartLayer ? 0u : 1); public override uint LayerIndexEnd => (uint)(LayerIndexStart + Count - 1); + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var result = new ConcurrentBag(); diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs index f5550c1..32bf0bc 100644 --- a/UVtools.Core/Operations/OperationLayerReHeight.cs +++ b/UVtools.Core/Operations/OperationLayerReHeight.cs @@ -14,14 +14,15 @@ using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationLayerReHeight : Operation { private OperationLayerReHeightItem _item; #region Overrides - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.None; - public override bool CanROI { get; set; } = false; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; + public override bool CanROI => false; public override string Title => "Adjust layer height"; public override string Description => "Adjust the layer height of the model\n\n" + @@ -38,6 +39,8 @@ namespace UVtools.Core.Operations public override bool CanCancel => false; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); diff --git a/UVtools.Core/Operations/OperationLayerRemove.cs b/UVtools.Core/Operations/OperationLayerRemove.cs index b2fb9ca..25dd621 100644 --- a/UVtools.Core/Operations/OperationLayerRemove.cs +++ b/UVtools.Core/Operations/OperationLayerRemove.cs @@ -5,16 +5,19 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; using System.Text; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationLayerRemove : Operation { #region Overrides - public override Enumerations.LayerRangeSelection LayerRangeSelection => Enumerations.LayerRangeSelection.Current; - public override bool CanROI { get; set; } = false; + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Current; + public override bool CanROI => false; public override bool PassActualLayerIndex => true; public override string Title => "Remove layers"; @@ -32,6 +35,8 @@ namespace UVtools.Core.Operations public override bool CanCancel => false; + public override bool CanHaveProfiles => false; + #endregion #region Properties diff --git a/UVtools.Core/Operations/OperationMask.cs b/UVtools.Core/Operations/OperationMask.cs index ed1c3c3..c702371 100644 --- a/UVtools.Core/Operations/OperationMask.cs +++ b/UVtools.Core/Operations/OperationMask.cs @@ -6,13 +6,16 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Text; +using System.Xml.Serialization; using Emgu.CV; using Emgu.CV.CvEnum; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationMask : Operation { public override string Title => "Mask"; @@ -30,6 +33,8 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Masked layers"; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); @@ -41,6 +46,7 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + [XmlIgnore] public Mat Mask { get; set; } public bool HaveMask => !(Mask is null); diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs index 9be71f7..a16d92c 100644 --- a/UVtools.Core/Operations/OperationMorph.cs +++ b/UVtools.Core/Operations/OperationMorph.cs @@ -6,11 +6,14 @@ * of this license document, but changing it is not allowed. */ +using System; +using System.Xml.Serialization; using Emgu.CV.CvEnum; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationMorph : Operation { private MorphOp _morphOperation = MorphOp.Erode; @@ -46,6 +49,19 @@ namespace UVtools.Core.Operations new StringTag("Gradient - Removes the interior areas of objects", MorphOp.Gradient), }; + public byte MorphOperationIndex + { + get + { + for (byte i = 0; i < MorphOperations.Length; i++) + { + if ((MorphOp) MorphOperations[i].Tag == MorphOperation) return i; + } + + return 0; + } + } + public MorphOp MorphOperation { get => _morphOperation; @@ -70,8 +86,42 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _fadeInOut, value); } + [XmlIgnore] public Kernel Kernel { get; set; } = new Kernel(); + public override string ToString() + { + var result = $"[{_morphOperation}] [Iterations: {_iterationsStart}/{_iterationsEnd}] [Fade: {_fadeInOut}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Equality + + private bool Equals(OperationMorph other) + { + return _morphOperation == other._morphOperation && _iterationsStart == other._iterationsStart && _iterationsEnd == other._iterationsEnd && _fadeInOut == other._fadeInOut; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationMorph other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (int) _morphOperation; + hashCode = (hashCode * 397) ^ (int) _iterationsStart; + hashCode = (hashCode * 397) ^ (int) _iterationsEnd; + hashCode = (hashCode * 397) ^ _fadeInOut.GetHashCode(); + return hashCode; + } + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs index 60c9840..b25b611 100644 --- a/UVtools.Core/Operations/OperationMove.cs +++ b/UVtools.Core/Operations/OperationMove.cs @@ -13,6 +13,7 @@ using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationMove : Operation { public override string Title => "Move"; @@ -29,6 +30,8 @@ namespace UVtools.Core.Operations public override string ProgressAction => (IsCutMove ? "Moved" : "Copied")+" layers"; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs index 4297f42..7d66f7d 100644 --- a/UVtools.Core/Operations/OperationPattern.cs +++ b/UVtools.Core/Operations/OperationPattern.cs @@ -5,12 +5,15 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; using System.Drawing; using System.Text; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationPattern : Operation { private Enumerations.Anchor _anchor = Enumerations.Anchor.None; @@ -37,6 +40,8 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Patterned layers"; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs index 22941d6..24bbeaa 100644 --- a/UVtools.Core/Operations/OperationPixelDimming.cs +++ b/UVtools.Core/Operations/OperationPixelDimming.cs @@ -6,12 +6,15 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Text; +using System.Xml.Serialization; using Emgu.CV; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationPixelDimming : Operation { private uint _borderSize = 5; @@ -68,12 +71,14 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _bordersOnly, value); } + [XmlIgnore] public Matrix EvenPattern { get => _evenPattern; set => RaiseAndSetIfChanged(ref _evenPattern, value); } + [XmlIgnore] public Matrix OddPattern { get => _oddPattern; @@ -81,5 +86,37 @@ namespace UVtools.Core.Operations } #endregion + + public override string ToString() + { + var result = $"[Border: {_borderSize}px] [Only borders: {_bordersOnly}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #region Equality + + protected bool Equals(OperationPixelDimming other) + { + return _borderSize == other._borderSize && _bordersOnly == other._bordersOnly; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((OperationPixelDimming) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((int) _borderSize * 397) ^ _bordersOnly.GetHashCode(); + } + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs index e004682..d54054e 100644 --- a/UVtools.Core/Operations/OperationRepairLayers.cs +++ b/UVtools.Core/Operations/OperationRepairLayers.cs @@ -6,12 +6,15 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Collections.Generic; using System.Text; +using System.Xml.Serialization; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationRepairLayers : Operation { private bool _repairIslands = true; @@ -21,7 +24,7 @@ namespace UVtools.Core.Operations private ushort _removeIslandsRecursiveIterations = 4; private uint _gapClosingIterations = 1; private uint _noiseRemovalIterations = 0; - public override bool CanROI { get; set; } = false; + public override bool CanROI => false; public override string Title => "Repair layers and issues"; public override string Description => null; @@ -32,6 +35,8 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Repaired layers"; + public override bool CanHaveProfiles => false; + public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); @@ -86,8 +91,10 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _noiseRemovalIterations, value); } + [XmlIgnore] public IslandDetectionConfiguration IslandDetectionConfig { get; set; } + [XmlIgnore] public List Issues { get; set; } } diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs index 2225067..fa20389 100644 --- a/UVtools.Core/Operations/OperationResize.cs +++ b/UVtools.Core/Operations/OperationResize.cs @@ -5,11 +5,14 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; using System.Text; using UVtools.Core.Objects; namespace UVtools.Core.Operations { + [Serializable] public class OperationResize : Operation { private decimal _x = 100; @@ -31,7 +34,7 @@ namespace UVtools.Core.Operations public override string ProgressTitle => $"Resizing from layers {LayerIndexStart} to {LayerIndexEnd} at X:{X}% Y:{Y}% "; - public override string ProgressAction => "Reiszed layers"; + public override string ProgressAction => "Resized layers"; public override StringTag Validate(params object[] parameters) { @@ -86,5 +89,41 @@ namespace UVtools.Core.Operations public OperationResize() { } + + public override string ToString() + { + var result = $"[X: {_x}%, Y: {_y}%] [Fade: {_isFade}] [Constrain: {_constrainXy}]"+ LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #region Equality + + protected bool Equals(OperationResize other) + { + return _x == other._x && _y == other._y && _constrainXy == other._constrainXy && _isFade == other._isFade; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((OperationResize) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = _x.GetHashCode(); + hashCode = (hashCode * 397) ^ _y.GetHashCode(); + hashCode = (hashCode * 397) ^ _constrainXy.GetHashCode(); + hashCode = (hashCode * 397) ^ _isFade.GetHashCode(); + return hashCode; + } + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs index e95991f..4d32762 100644 --- a/UVtools.Core/Operations/OperationRotate.cs +++ b/UVtools.Core/Operations/OperationRotate.cs @@ -10,6 +10,7 @@ using System; namespace UVtools.Core.Operations { + [Serializable] public class OperationRotate : Operation { private decimal _angleDegrees = 90; @@ -30,5 +31,34 @@ namespace UVtools.Core.Operations get => _angleDegrees; set => RaiseAndSetIfChanged(ref _angleDegrees, value); } + + public override string ToString() + { + var result = $"[Angle: {Math.Abs(_angleDegrees)}º {(AngleDegrees < 0 ? "CCW" : "CW")}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #region Equality + + protected bool Equals(OperationRotate other) + { + return _angleDegrees == other._angleDegrees; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((OperationRotate) obj); + } + + public override int GetHashCode() + { + return _angleDegrees.GetHashCode(); + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs index fc6aa96..af9474c 100644 --- a/UVtools.Core/Operations/OperationSolidify.cs +++ b/UVtools.Core/Operations/OperationSolidify.cs @@ -6,8 +6,11 @@ * of this license document, but changing it is not allowed. */ +using System; + namespace UVtools.Core.Operations { + [Serializable] public sealed class OperationSolidify : Operation { #region Overrides @@ -26,6 +29,8 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Solidified layers"; + public override bool CanHaveProfiles => false; + #endregion } } diff --git a/UVtools.Core/Operations/OperationThreshold.cs b/UVtools.Core/Operations/OperationThreshold.cs index 8854ac7..143f173 100644 --- a/UVtools.Core/Operations/OperationThreshold.cs +++ b/UVtools.Core/Operations/OperationThreshold.cs @@ -11,6 +11,7 @@ using Emgu.CV.CvEnum; namespace UVtools.Core.Operations { + [Serializable] public class OperationThreshold : Operation { private byte _threshold = 127; @@ -51,5 +52,40 @@ namespace UVtools.Core.Operations } public static Array ThresholdTypes => Enum.GetValues(typeof(ThresholdType)); + + public override string ToString() + { + var result = $"[{_type} = {_threshold} / {_maximum}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #region Equality + + protected bool Equals(OperationThreshold other) + { + return _threshold == other._threshold && _maximum == other._maximum && _type == other._type; + } + + 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; + return Equals((OperationThreshold) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = _threshold.GetHashCode(); + hashCode = (hashCode * 397) ^ _maximum.GetHashCode(); + hashCode = (hashCode * 397) ^ (int) _type; + return hashCode; + } + } + + #endregion } } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 4486203..1966dac 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.2 + 1.1.3 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index b946e10..8df3fa8 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -20,7 +20,9 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.ThemeManager; using Emgu.CV; +using UVtools.Core; using UVtools.Core.FileFormats; +using UVtools.Core.Operations; using UVtools.WPF.Extensions; using UVtools.WPF.Structures; @@ -49,6 +51,8 @@ namespace UVtools.WPF UserSettings.Load(); UserSettings.SetVersion(); + OperationProfiles.Load(); + ThemeSelector = Avalonia.ThemeManager.ThemeSelector.Create(Path.Combine(ApplicationPath, "Assets", "Themes")); ThemeSelector.LoadSelectedTheme(Path.Combine(UserSettings.SettingsFolder, "selected.theme")); if (ThemeSelector.SelectedTheme.Name == "UVtoolsDark" || ThemeSelector.SelectedTheme.Name == "Light") @@ -87,25 +91,24 @@ namespace UVtools.WPF } #region Utilities + + public static string AppExecutable = Path.Combine(ApplicationPath, About.Software); + public static string AppExecutableQuoted = $"\"{AppExecutable}\""; public static void NewInstance(string filePath) { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var info = new ProcessStartInfo("UVtools.exe", $"\"{filePath}\"") - { - UseShellExecute = true - }; - Process.Start(info).Dispose(); + StartProcess($"{AppExecutable}.exe", $"\"{filePath}\""); } - else if(File.Exists("UVtools")) + else if(File.Exists(AppExecutable)) // Direct execute { - Process.Start("UVtools", $"\"{filePath}\"").Dispose(); + StartProcess(AppExecutable, $"\"{filePath}\""); } else { - Process.Start("dotnet", $"UVtools.dll \"{filePath}\"").Dispose(); + StartProcess("dotnet", $"UVtools.dll \"{filePath}\""); } } catch (Exception e) @@ -142,11 +145,11 @@ namespace UVtools.WPF } - public static void StartProcess(string name) + public static void StartProcess(string name, string arguments = null) { try { - using (Process.Start(new ProcessStartInfo(name) + using (Process.Start(new ProcessStartInfo(name, arguments) { UseShellExecute = true })) { } @@ -155,7 +158,6 @@ namespace UVtools.WPF { Debug.WriteLine(e); } - } public static Stream GetAsset(string url) diff --git a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs index 73fef89..623633e 100644 --- a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs @@ -6,12 +6,12 @@ namespace UVtools.WPF.Controls.Tools { public class ToolArithmeticControl : ToolControl { - public OperationArithmetic Operation { get; } + public OperationArithmetic Operation => BaseOperation as OperationArithmetic; public ToolArithmeticControl() { InitializeComponent(); - BaseOperation = Operation = new OperationArithmetic(); + BaseOperation = new OperationArithmetic(); Operation.PropertyChanged += (sender, e) => { if (e.PropertyName == nameof(Operation.Sentence)) diff --git a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs index 4a03116..88058ec 100644 --- a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs @@ -10,7 +10,7 @@ namespace UVtools.WPF.Controls.Tools private bool _isSizeEnabled = true; private bool _isKernelVisible; private KernelControl _kernelCtrl; - public OperationBlur Operation { get; } + public OperationBlur Operation => BaseOperation as OperationBlur; public int SelectedAlgorithmIndex { @@ -40,7 +40,7 @@ namespace UVtools.WPF.Controls.Tools public ToolBlurControl() { InitializeComponent(); - BaseOperation = Operation = new OperationBlur(); + BaseOperation = new OperationBlur(); _kernelCtrl = this.Find("KernelCtrl"); } diff --git a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs index f8acf19..c342418 100644 --- a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs @@ -6,14 +6,14 @@ namespace UVtools.WPF.Controls.Tools { public class ToolCalculatorControl : ToolControl { - public OperationCalculator Operation { get; } + public OperationCalculator Operation => BaseOperation as OperationCalculator; public FileFormat SlicerFile => App.SlicerFile; public ToolCalculatorControl() { InitializeComponent(); - BaseOperation = Operation = new OperationCalculator + BaseOperation = new OperationCalculator { CalcMillimetersToPixels = new OperationCalculator.MillimetersToPixels(SlicerFile.Resolution, SlicerFile.Display), CalcLightOffDelay = new OperationCalculator.LightOffDelayC( diff --git a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs index 5f47e72..2762a3c 100644 --- a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs @@ -8,7 +8,7 @@ namespace UVtools.WPF.Controls.Tools public class ToolChangeResolutionControl : ToolControl { private OperationChangeResolution.Resolution _selectedPresetItem; - public OperationChangeResolution Operation { get; } + public OperationChangeResolution Operation => BaseOperation as OperationChangeResolution; public OperationChangeResolution.Resolution SelectedPresetItem { @@ -34,7 +34,7 @@ namespace UVtools.WPF.Controls.Tools public ToolChangeResolutionControl() { InitializeComponent(); - BaseOperation = Operation = new OperationChangeResolution(App.SlicerFile.Resolution, App.SlicerFile.LayerManager.BoundingRectangle); + BaseOperation = new OperationChangeResolution(App.SlicerFile.Resolution, App.SlicerFile.LayerManager.BoundingRectangle); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs index e91f2fd..8ec6d5b 100644 --- a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs @@ -9,7 +9,19 @@ namespace UVtools.WPF.Controls.Tools { public class ToolControl : UserControlEx { - public Operation BaseOperation = null; + private Operation _baseOperation; + + public Operation BaseOperation + { + get => _baseOperation; + set + { + if(!RaiseAndSetIfChanged(ref _baseOperation, value)) return; + if (DataContext is null) return; + ResetDataContext(); + } + } + public ToolWindow ParentWindow { get; set; } = null; public bool CanRun { get; set; } = true; @@ -33,6 +45,12 @@ namespace UVtools.WPF.Controls.Tools public virtual bool UpdateOperation() => true; + /*public virtual void SetOperation(Operation operation) + { + BaseOperation = operation; + ResetDataContext(); + }*/ + /// /// Validates if is safe to continue with operation /// diff --git a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs index d1e761b..ba83fec 100644 --- a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs @@ -16,7 +16,7 @@ namespace UVtools.WPF.Controls.Tools { public class ToolEditParametersControl : ToolControl { - public OperationEditParameters Operation { get; } + public OperationEditParameters Operation => BaseOperation as OperationEditParameters; public bool SupportPerLayerSettings => App.SlicerFile.SupportPerLayerSettings; public RowControl[] RowControls; @@ -110,7 +110,7 @@ namespace UVtools.WPF.Controls.Tools InitializeComponent(); App.SlicerFile.RefreshPrintParametersModifiersValues(); - BaseOperation = Operation = new OperationEditParameters(App.SlicerFile.PrintParameterModifiers); + BaseOperation = new OperationEditParameters(App.SlicerFile.PrintParameterModifiers); if (Operation.Modifiers is null || Operation.Modifiers.Length == 0) { diff --git a/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs index c2d973b..7899b4d 100644 --- a/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs @@ -5,12 +5,12 @@ namespace UVtools.WPF.Controls.Tools { public class ToolFlipControl : ToolControl { - public OperationFlip Operation { get; } + public OperationFlip Operation => BaseOperation as OperationFlip; public ToolFlipControl() { InitializeComponent(); - BaseOperation = Operation = new OperationFlip(); + BaseOperation = new OperationFlip(); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs index a394fa5..bbc7776 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs @@ -6,7 +6,7 @@ namespace UVtools.WPF.Controls.Tools { public class ToolLayerCloneControl : ToolControl { - public OperationLayerClone Operation { get; } + public OperationLayerClone Operation => BaseOperation as OperationLayerClone; public uint ExtraLayers => (uint)Math.Max(0, ((int)Operation.LayerIndexEnd - Operation.LayerIndexStart + 1) * Operation.Clones); @@ -31,7 +31,7 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerCloneControl() { InitializeComponent(); - BaseOperation = Operation = new OperationLayerClone(); + BaseOperation = new OperationLayerClone(); Operation.PropertyChanged += (sender, args) => { RaisePropertyChanged(nameof(InfoLayersStr)); diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs index 7e08757..f96b5f9 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Controls.Shapes; using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; @@ -12,7 +11,6 @@ using UVtools.Core.Objects; using UVtools.Core.Operations; using UVtools.WPF.Extensions; using UVtools.WPF.Windows; -using Path = System.IO.Path; namespace UVtools.WPF.Controls.Tools { @@ -23,8 +21,8 @@ namespace UVtools.WPF.Controls.Tools private Bitmap _previewImage; private ListBox FilesListBox; - - public OperationLayerImport Operation { get; } + + public OperationLayerImport Operation => BaseOperation as OperationLayerImport; public uint MaximumLayer => App.SlicerFile.LastLayerIndex; @@ -83,7 +81,7 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerImportControl() { InitializeComponent(); - BaseOperation = Operation = new OperationLayerImport(App.SlicerFile.Resolution); + BaseOperation = new OperationLayerImport(App.SlicerFile.Resolution); Operation.Files.CollectionChanged += (sender, args) => RefreshGUI(); Operation.PropertyChanged += (sender, args) => RefreshGUI(); FilesListBox = this.Find("FilesListBox"); diff --git a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs index 88c0d0a..feb5e56 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs @@ -6,7 +6,7 @@ namespace UVtools.WPF.Controls.Tools { public class ToolLayerReHeightControl : ToolControl { - public OperationLayerReHeight Operation { get; } + public OperationLayerReHeight Operation => BaseOperation as OperationLayerReHeight; public OperationLayerReHeight.OperationLayerReHeightItem[] Presets { get; } = OperationLayerReHeight.GetItems( App.SlicerFile.LayerCount, @@ -17,7 +17,7 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerReHeightControl() { InitializeComponent(); - BaseOperation = Operation = new OperationLayerReHeight(); + BaseOperation = new OperationLayerReHeight(); if (Presets.Length == 0) { diff --git a/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs index e025811..a2ada0e 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs @@ -6,7 +6,7 @@ namespace UVtools.WPF.Controls.Tools { public class ToolLayerRemoveControl : ToolControl { - public OperationLayerRemove Operation { get; } + public OperationLayerRemove Operation => BaseOperation as OperationLayerRemove; public uint ExtraLayers => (uint)Math.Max(0, (int)Operation.LayerIndexEnd - Operation.LayerIndexStart + 1); @@ -31,7 +31,7 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerRemoveControl() { InitializeComponent(); - BaseOperation = Operation = new OperationLayerRemove(); + BaseOperation = new OperationLayerRemove(); Operation.PropertyChanged += (sender, args) => { RaisePropertyChanged(nameof(InfoLayersStr)); diff --git a/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml.cs index 62486b7..bad26b4 100644 --- a/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml.cs @@ -20,7 +20,7 @@ namespace UVtools.WPF.Controls.Tools private byte _genMaximumBrightness = byte.MaxValue; private uint _genDiameter; private Bitmap _maskImage; - public OperationMask Operation { get; } + public OperationMask Operation => BaseOperation as OperationMask; public bool IsMaskInverted { @@ -69,7 +69,7 @@ namespace UVtools.WPF.Controls.Tools public ToolMaskControl() { InitializeComponent(); - BaseOperation = Operation = new OperationMask(); + BaseOperation = new OperationMask(); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs index 5384461..7dac93e 100644 --- a/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Emgu.CV.CvEnum; @@ -10,7 +11,7 @@ namespace UVtools.WPF.Controls.Tools public class ToolMorphControl : ToolControl { private byte _morphSelectedIndex; - public OperationMorph Operation { get; } + public OperationMorph Operation => BaseOperation as OperationMorph; private KernelControl _kernelCtrl; @@ -27,7 +28,7 @@ namespace UVtools.WPF.Controls.Tools public ToolMorphControl() { InitializeComponent(); - BaseOperation = Operation = new OperationMorph(); + BaseOperation = new OperationMorph(); _kernelCtrl = this.Find("KernelCtrl"); } diff --git a/UVtools.WPF/Controls/Tools/ToolMoveControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolMoveControl.axaml.cs index 03a604e..a18fc5f 100644 --- a/UVtools.WPF/Controls/Tools/ToolMoveControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolMoveControl.axaml.cs @@ -7,7 +7,7 @@ namespace UVtools.WPF.Controls.Tools public class ToolMoveControl : ToolControl { private bool _isMiddleCenterChecked = true; - public OperationMove Operation { get; } + public OperationMove Operation => BaseOperation as OperationMove; public bool IsMiddleCenterChecked { @@ -19,7 +19,7 @@ namespace UVtools.WPF.Controls.Tools { InitializeComponent(); var roi = App.MainWindow.ROI; - BaseOperation = Operation = new OperationMove(roi.IsEmpty ? App.SlicerFile.LayerManager.BoundingRectangle : roi, (uint)App.MainWindow.LayerCache.Image.Width, + BaseOperation = new OperationMove(roi.IsEmpty ? App.SlicerFile.LayerManager.BoundingRectangle : roi, (uint)App.MainWindow.LayerCache.Image.Width, (uint)App.MainWindow.LayerCache.Image.Height); Operation.PropertyChanged += (sender, e) => diff --git a/UVtools.WPF/Controls/Tools/ToolPatternControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPatternControl.axaml.cs index 1b2bbc9..cb29ad4 100644 --- a/UVtools.WPF/Controls/Tools/ToolPatternControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolPatternControl.axaml.cs @@ -8,7 +8,7 @@ namespace UVtools.WPF.Controls.Tools { public class ToolPatternControl : ToolControl { - public OperationPattern Operation { get; } + public OperationPattern Operation => BaseOperation as OperationPattern; private bool _isDefaultAnchorChecked = true; public bool IsDefaultAnchorChecked @@ -21,7 +21,7 @@ namespace UVtools.WPF.Controls.Tools { InitializeComponent(); var roi = App.MainWindow.ROI; - BaseOperation = Operation = new OperationPattern(roi.IsEmpty ? App.SlicerFile.LayerManager.BoundingRectangle : roi, App.SlicerFile.Resolution); + BaseOperation = new OperationPattern(roi.IsEmpty ? App.SlicerFile.LayerManager.BoundingRectangle : roi, App.SlicerFile.Resolution); if (Operation.MaxRows < 2 && Operation.MaxCols < 2) { diff --git a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs index 24bdd26..36499a2 100644 --- a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs @@ -28,7 +28,7 @@ namespace UVtools.WPF.Controls.Tools private byte _dimGenBrightness = 127; private ushort _infillGenThickness = 10; private ushort _infillGenSpacing = 20; - public OperationPixelDimming Operation { get; } + public OperationPixelDimming Operation => BaseOperation as OperationPixelDimming; public string PatternText { @@ -63,7 +63,7 @@ namespace UVtools.WPF.Controls.Tools public ToolPixelDimmingControl() { InitializeComponent(); - BaseOperation = Operation = new OperationPixelDimming(); + BaseOperation = new OperationPixelDimming(); GeneratePixelDimming("Chessboard"); } diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs index 843c096..0c317d8 100644 --- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs @@ -6,12 +6,12 @@ namespace UVtools.WPF.Controls.Tools { public class ToolRepairLayersControl : ToolControl { - public OperationRepairLayers Operation { get; } + public OperationRepairLayers Operation => BaseOperation as OperationRepairLayers; public ToolRepairLayersControl() { InitializeComponent(); - BaseOperation = Operation = new OperationRepairLayers + BaseOperation = new OperationRepairLayers { RepairIslands = UserSettings.Instance.LayerRepair.RepairIslands, RepairResinTraps = UserSettings.Instance.LayerRepair.RepairResinTraps, diff --git a/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml.cs index 2f66254..c88b965 100644 --- a/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml.cs @@ -1,17 +1,16 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml; using UVtools.Core.Operations; namespace UVtools.WPF.Controls.Tools { public class ToolResizeControl : ToolControl { - public OperationResize Operation { get; } + public OperationResize Operation => BaseOperation as OperationResize; public ToolResizeControl() { InitializeComponent(); - BaseOperation = Operation = new OperationResize(); + BaseOperation = new OperationResize(); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml b/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml index cf5d180..832c0e6 100644 --- a/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml @@ -9,7 +9,7 @@ BaseOperation as OperationRotate; public ToolRotateControl() { InitializeComponent(); - BaseOperation = Operation = new OperationRotate(); + BaseOperation = new OperationRotate(); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs index 5809c8b..648bbbf 100644 --- a/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs @@ -10,7 +10,7 @@ namespace UVtools.WPF.Controls.Tools private bool _isThresholdEnabled = true; private bool _isMaximumEnabled = true; private bool _isTypeEnabled = true; - public OperationThreshold Operation { get; } + public OperationThreshold Operation => BaseOperation as OperationThreshold; public string[] Presets => new[] { @@ -74,7 +74,7 @@ namespace UVtools.WPF.Controls.Tools public ToolThresholdControl() { InitializeComponent(); - BaseOperation = Operation = new OperationThreshold(); + BaseOperation = new OperationThreshold(); } private void InitializeComponent() diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index fa2de86..1cff077 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -688,7 +688,49 @@ namespace UVtools.WPF await new PrusaSlicerManager().ShowDialog(this); } - public void MenuNewVersionClicked() => App.OpenBrowser(VersionChecker.Url); + public async void MenuNewVersionClicked() + { + var result = + await this.MessageBoxQuestion( + $"Do you like to auto-update {About.Software} v{AppSettings.Version} to v{VersionChecker.Version}?\n\n" + + "Yes: Auto update\n" + + "No: Manual update\n" + + "Cancel: No action", "Update UVtools?", ButtonEnum.YesNoCancel); + + if (result == ButtonResult.Yes) + { + IsGUIEnabled = false; + + var task = await Task.Factory.StartNew(async () => + { + ShowProgressWindow($"Downloading: {VersionChecker.Filename}"); + try + { + VersionChecker.AutoUpgrade(ProgressWindow.RestartProgress(false)); + return true; + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + Dispatcher.UIThread.InvokeAsync(async () => + await this.MessageBoxError(exception.ToString(), "Error opening the file")); + } + + return false; + }); + + IsGUIEnabled = true; + + return; + } + if (result == ButtonResult.No) + { + App.OpenBrowser(VersionChecker.UrlLatestRelease); + return; + } + } #endregion diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs index 1d7fa50..efd7797 100644 --- a/UVtools.WPF/Program.cs +++ b/UVtools.WPF/Program.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics; using Avalonia; +using Avalonia.Threading; +using UVtools.WPF.Extensions; namespace UVtools.WPF { @@ -18,10 +20,28 @@ namespace UVtools.WPF ProgramStartupTime = Stopwatch.StartNew(); Args = args; + // Add the event handler for handling non-UI thread exceptions to the event. + AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException; + BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); } + private static async void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + try + { + Exception ex = (Exception)e.ExceptionObject; + string errorMsg = "An application error occurred. Please contact the administrator with the following information:\n\n" + + $"{ex}"; + + await App.MainWindow.MessageBoxError(errorMsg, "Fatal Non-UI Error"); + } + catch (Exception exc) + { + } + } + // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() diff --git a/UVtools.WPF/Structures/AppVersionChecker.cs b/UVtools.WPF/Structures/AppVersionChecker.cs index 3d843f1..0d9f333 100644 --- a/UVtools.WPF/Structures/AppVersionChecker.cs +++ b/UVtools.WPF/Structures/AppVersionChecker.cs @@ -6,19 +6,72 @@ * of this license document, but changing it is not allowed. */ using System; +using System.Collections.Specialized; using System.Diagnostics; +using System.IO; +using System.IO.Compression; using System.Net; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using ABI.Windows.Data.Json; using Avalonia.Threading; +using Newtonsoft.Json; using UVtools.Core; using UVtools.Core.Objects; +using UVtools.Core.Operations; +using UVtools.WPF.Extensions; namespace UVtools.WPF.Structures { public class AppVersionChecker : BindableBase { + public const string GitHubReleaseApi = "https://api.github.com/repos/sn4k3/UVtools/releases/latest"; private string _version; private string _url; + public string Filename + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $"{About.Software}_win-x64_v{_version}.msi"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return $"{About.Software}_linux-x64_v{_version}.zip"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return $"{About.Software}_osx-x64_v{_version}.zip"; + } + + return $"{About.Software}_universal-x86-x64_v{_version}.zip"; + } + } + + public string Runtime + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return "win-x64"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "linux-x64"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "osx-x64"; + } + + return "universal-x86-x64"; + } + } + public string Version { get => _version; @@ -30,19 +83,46 @@ namespace UVtools.WPF.Structures } } - public string VersionAnnouncementText => $"New version {_version} is available!"; + public string VersionAnnouncementText => $"New version v{_version} is available!"; - public string Url => $"{About.Website}/releases/tag/{_version}"; + public string UrlLatestRelease => $"{About.Website}/releases/latest"; + + public string DownloadLink => string.IsNullOrEmpty(Filename) ? null : $"{About.Website}/releases/download/v{_version}/{Filename}"; public bool HaveNewVersion => !string.IsNullOrEmpty(Version); + public string DownloadedFile { get; private set; } + public bool Check() { try { - using (WebClient client = new WebClient()) + using (WebClient client = new WebClient { - string htmlCode = client.DownloadString($"{About.Website}/releases"); + Headers = new WebHeaderCollection + { + {HttpRequestHeader.Accept, "application/json"}, + {HttpRequestHeader.UserAgent, "Request"} + } + }) + { + var response = client.DownloadString(GitHubReleaseApi); + dynamic json = JsonConvert.DeserializeObject(response); + string tag_name = json.tag_name; + if (string.IsNullOrEmpty(tag_name)) return false; + tag_name = tag_name.Trim(' ', 'v', 'V'); + Debug.WriteLine($"Version checker: v{AppSettings.VersionStr} <=> v{tag_name}"); + + if (string.Compare(tag_name, AppSettings.VersionStr, StringComparison.OrdinalIgnoreCase) > 0) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Version = tag_name; + Debug.WriteLine($"New version detected: {DownloadLink}"); + }); + return true; + } + /*string htmlCode = client.DownloadString($"{About.Website}/releases"); const string searchFor = "/releases/tag/"; var startIndex = htmlCode.IndexOf(searchFor, StringComparison.InvariantCultureIgnoreCase) + searchFor.Length; @@ -55,7 +135,7 @@ namespace UVtools.WPF.Structures Version = version;; }); return true; - } + }*/ } } catch (Exception e) @@ -63,6 +143,104 @@ namespace UVtools.WPF.Structures Debug.WriteLine(e.ToString()); } + return false; + } + + public bool AutoUpgrade(OperationProgress progress) + { + if (!HaveNewVersion) return false; + progress.ItemName = "Megabytes"; + try + { + using (var client = new WebClient()) + { + var path = Path.GetTempPath(); + DownloadedFile = Path.Combine(path, Filename); + Debug.WriteLine($"Downloading to: {DownloadedFile}"); + + + client.DownloadProgressChanged += (sender, e) => + { + progress.Reset("Megabytes", (uint)e.TotalBytesToReceive / 1048576, (uint)e.BytesReceived / 1048576); + }; + client.DownloadFileCompleted += (sender, e) => + { + progress.Reset("Extracting"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + App.StartProcess(DownloadedFile); + Environment.Exit(0); + } + else + { + string upgradeFolder = "UPDATED_VERSION"; + var targetDir = Path.Combine(App.ApplicationPath, upgradeFolder); + using (var stream = File.Open(DownloadedFile, FileMode.Open)) + { + using (ZipArchive zip = new ZipArchive(stream, ZipArchiveMode.Read)) + { + zip.ExtractToDirectory(targetDir, true); + } + } + + File.Delete(DownloadedFile); + + string upgradeFileName = $"{About.Software}_upgrade.sh"; + var upgradeFile = Path.Combine(App.ApplicationPath, upgradeFileName); + using (var stream = File.CreateText(upgradeFile)) + { + stream.WriteLine("#!/bin/bash"); + stream.WriteLine($"echo {About.Software} v{AppSettings.Version} updater script"); + stream.WriteLine($"cd '{App.ApplicationPath}'"); + stream.WriteLine($"killall {About.Software}"); + stream.WriteLine("sleep 0.5"); + stream.WriteLine($"cp -fR {upgradeFolder}/* ."); + stream.WriteLine($"rm -fr {upgradeFolder}"); + stream.WriteLine("sleep 0.5"); + //stream.WriteLine($"[ -f {About.Software} ] && {App.AppExecutableQuoted} & || dotnet {About.Software}.dll &"); + stream.WriteLine($"if [ -f '{About.Software}' ]; then"); + stream.WriteLine($" ./{About.Software} &"); + stream.WriteLine("else"); + stream.WriteLine($" dotnet {About.Software}.dll &"); + stream.WriteLine("fi"); + stream.WriteLine($"rm -f {upgradeFileName}"); + //stream.WriteLine("exit"); + stream.Close(); + } + + App.StartProcess("bash", $"\"{upgradeFile}\""); + + //App.NewInstance(App.MainWindow.SlicerFile?.FileFullPath); + Environment.Exit(0); + } + + lock (e.UserState) + { + //releases blocked thread + Monitor.Pulse(e.UserState); + } + }; + + + var syncObject = new object(); + + lock (syncObject) + { + client.DownloadFileAsync(new Uri(DownloadLink), DownloadedFile, syncObject); + //This would block the thread until download completes + Monitor.Wait(syncObject); + } + + } + } + catch (Exception e) + { + Dispatcher.UIThread.InvokeAsync(async () => + await App.MainWindow.MessageBoxError(e.ToString(), "Error downloading the file")); + return false; + } + + return false; } } diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs new file mode 100644 index 0000000..c351776 --- /dev/null +++ b/UVtools.WPF/Structures/OperationProfiles.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml.Serialization; +using UVtools.Core.Operations; + +namespace UVtools.WPF.Structures +{ + [Serializable] + public class OperationProfiles //: IList + { + #region Properties + /// + /// Default filepath for store + /// + private static string FilePath => Path.Combine(UserSettings.SettingsFolder, "operation_profiles.xml"); + + [XmlElement(typeof(OperationArithmetic))] + [XmlElement(typeof(OperationBlur))] + //[XmlElement(typeof(OperationCalculator))] + [XmlElement(typeof(OperationChangeResolution))] + //[XmlElement(typeof(OperationEditParameters))] + [XmlElement(typeof(OperationFlip))] + //[XmlElement(typeof(OperationLayerClone))] + //[XmlElement(typeof(OperationLayerImport))] + //[XmlElement(typeof(OperationLayerReHeight))] + //[XmlElement(typeof(OperationLayerRemove))] + //[XmlElement(typeof(OperationMask))] + [XmlElement(typeof(OperationMorph))] + //[XmlElement(typeof(OperationMove))] + //[XmlElement(typeof(OperationPattern))] + [XmlElement(typeof(OperationPixelDimming))] + //[XmlElement(typeof(OperationRepairLayers))] + [XmlElement(typeof(OperationResize))] + [XmlElement(typeof(OperationRotate))] + [XmlElement(typeof(OperationThreshold))] + public List Operations { get; internal set; } = new List(); + + [XmlIgnore] + public static List Profiles + { + get => Instance.Operations; + internal set => Instance.Operations = value; + } + + #endregion + + #region Singleton + private static OperationProfiles _instance; + /// + /// Instance of (singleton) + /// + public static OperationProfiles Instance + { + get => _instance ??= new OperationProfiles(); + internal set => _instance = value; + } + + //public static List Operations => _instance.Operations; + #endregion + + #region Constructor + + private OperationProfiles() + { } + + #endregion + + #region Indexers + + public Operation this[uint index] + { + get => Operations[(int) index]; + set => Operations[(int) index] = value; + } + + public Operation this[int index] + { + get => Operations[index]; + set => Operations[index] = value; + } + + #endregion + + #region Enumerable + + public IEnumerator GetEnumerator() + { + return Operations.GetEnumerator(); + } + + /*IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + }*/ + #endregion + + #region List Implementation + public void Add(Operation item) + { + Operations.Add(item); + } + + public int IndexOf(Operation item) + { + return Operations.IndexOf(item); + } + + public void Insert(int index, Operation item) + { + Operations.Insert(index, item); + } + + public bool Remove(Operation item) + { + return Operations.Remove(item); + } + + public void RemoveAt(int index) + { + Operations.RemoveAt(index); + } + + public void Clear() + { + Operations.Clear(); + } + + public bool Contains(Operation item) + { + return Operations.Contains(item); + } + + public void CopyTo(Operation[] array, int arrayIndex) + { + Operations.CopyTo(array, arrayIndex); + } + + public int Count => Operations.Count; + public bool IsReadOnly => false; + #endregion + + #region Static Methods + /// + /// Clear all profiles + /// + /// True to save settings on file, otherwise false + public static void ClearProfiles(bool save = true) + { + Instance.Clear(); + if (save) Save(); + } + + /// + /// Clear all profiles given a type + /// + /// Type profiles to clear + /// True to save settings on file, otherwise false + public static void ClearProfiles(Type type, bool save = true) + { + Profiles.RemoveAll(operation => operation.GetType() == type); + if (save) Save(); + } + + /// + /// Load settings from file + /// + public static void Load() + { + if (!File.Exists(FilePath)) + { + return; + } + + var serializer = new XmlSerializer(Instance.GetType()); + try + { + using var myXmlReader = new StreamReader(FilePath); + _instance = (OperationProfiles)serializer.Deserialize(myXmlReader); + } + catch (Exception e) + { + Debug.WriteLine(e.ToString()); + ClearProfiles(); + } + } + + /// + /// Save settings to file + /// + public static void Save() + { + var serializer = new XmlSerializer(Instance.GetType()); + try + { + using var myXmlWriter = new StreamWriter(FilePath); + serializer.Serialize(myXmlWriter, Instance); + } + catch (Exception e) + { + Debug.WriteLine(e.ToString()); + } + } + + public static Operation FindByName(Operation baseOperation, string profileName) + { + return Profiles.Find(operation => + operation.GetType() == baseOperation.GetType() && + ( + !string.IsNullOrWhiteSpace(profileName) && operation.ProfileName == profileName + || operation.Equals(baseOperation) + )); + } + + + /// + /// Adds a profile + /// + /// + /// + public static void AddProfile(Operation operation, bool save = true) + { + Profiles.Insert(0, operation); + if(save) Save(); + } + + /// + /// Removes a profile + /// + /// + /// + public static void RemoveProfile(Operation operation, bool save = true) + { + Instance.Remove(operation); + if (save) Save(); + } + + /// + /// Get all profiles within a type + /// + /// + /// + public static List GetOperations(Type type) + => Profiles.Where(operation => operation.GetType() == type).ToList(); + + /// + /// Get all profiles within a type + /// + /// + /// + public static List GetOperations() + { + var result = new List(); + foreach (var operation in _instance) + { + if (operation is T operationCast) + { + result.Add(operationCast); + } + } + + return result; + } + + #endregion + } +} diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index b0ddb53..005df41 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ LICENSE https://github.com/sn4k3/UVtools Git - 1.1.2 + 1.1.3 @@ -29,7 +29,6 @@ - diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index c6dc4bb..28fbe6e 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -9,7 +9,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; using System.Xml.Serialization; using Avalonia.Media; using JetBrains.Annotations; @@ -951,7 +950,16 @@ namespace UVtools.WPF get { var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), About.Software); - Directory.CreateDirectory(path); + try + { + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + return path; } } diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml index 0fba631..4c37dda 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml +++ b/UVtools.WPF/Windows/ToolWindow.axaml @@ -202,6 +202,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _profiles = new ObservableCollection(); + private Operation _selectedProfileItem; + private string _profileText; + private IControl _contentControl; private bool _isButton1Visible; private string _button1Text = "Reset to defaults"; @@ -42,6 +54,8 @@ namespace UVtools.WPF.Windows private string _buttonOkText = "Ok"; private bool _buttonOkVisible = true; private double _scrollViewerMaxHeight=double.PositiveInfinity; + + public double ScrollViewerMaxHeight { @@ -92,8 +106,12 @@ namespace UVtools.WPF.Windows set { if (!(ToolControl?.BaseOperation is null)) + { + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None; ToolControl.BaseOperation.LayerIndexStart = value; - + } + + value = value.Clamp(0, App.SlicerFile.LastLayerIndex); if (!RaiseAndSetIfChanged(ref _layerIndexStart, value)) return; RaisePropertyChanged(nameof(LayerStartMM)); RaisePropertyChanged(nameof(LayerRangeCountStr)); @@ -115,8 +133,12 @@ namespace UVtools.WPF.Windows set { if (!(ToolControl?.BaseOperation is null)) + { + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None; ToolControl.BaseOperation.LayerIndexEnd = value; + } + value = value.Clamp(0, App.SlicerFile.LastLayerIndex); if (!RaiseAndSetIfChanged(ref _layerIndexEnd, value)) return; RaisePropertyChanged(nameof(LayerEndMM)); RaisePropertyChanged(nameof(LayerRangeCountStr)); @@ -141,33 +163,74 @@ namespace UVtools.WPF.Windows { LayerIndexStart = 0; LayerIndexEnd = MaximumLayerIndex; + if(!(ToolControl is null)) + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.All; } public void SelectCurrentLayer() { LayerIndexStart = LayerIndexEnd = App.MainWindow.ActualLayer; + if (!(ToolControl is null)) + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Current; } public void SelectBottomLayers() { LayerIndexStart = 0; LayerIndexEnd = App.SlicerFile.BottomLayerCount-1u; + if (!(ToolControl is null)) + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Bottom; } public void SelectNormalLayers() { LayerIndexStart = App.SlicerFile.BottomLayerCount; LayerIndexEnd = MaximumLayerIndex; + if (!(ToolControl is null)) + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Normal; } public void SelectFirstLayer() { LayerIndexStart = LayerIndexEnd = 0; + if (!(ToolControl is null)) + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.First; } public void SelectLastLayer() { LayerIndexStart = LayerIndexEnd = MaximumLayerIndex; + if (!(ToolControl is null)) + ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Last; + } + + public void SelectLayers(Enumerations.LayerRangeSelection range) + { + switch (range) + { + case Enumerations.LayerRangeSelection.None: + break; + case Enumerations.LayerRangeSelection.All: + SelectAllLayers(); + break; + case Enumerations.LayerRangeSelection.Current: + SelectCurrentLayer(); + break; + case Enumerations.LayerRangeSelection.Bottom: + SelectBottomLayers(); + break; + case Enumerations.LayerRangeSelection.Normal: + SelectNormalLayers(); + break; + case Enumerations.LayerRangeSelection.First: + SelectFirstLayer(); + break; + case Enumerations.LayerRangeSelection.Last: + SelectLastLayer(); + break; + default: + throw new ArgumentOutOfRangeException(); + } } #endregion @@ -203,6 +266,98 @@ namespace UVtools.WPF.Windows #endregion + #region Profiles + public bool IsProfilesVisible + { + get => _isProfilesVisible; + set => RaiseAndSetIfChanged(ref _isProfilesVisible, value); + } + + public ObservableCollection Profiles + { + get => _profiles; + set => RaiseAndSetIfChanged(ref _profiles, value); + } + + public Operation SelectedProfileItem + { + get => _selectedProfileItem; + set + { + if(!RaiseAndSetIfChanged(ref _selectedProfileItem, value) || value is null) return; + var operation = _selectedProfileItem.Clone(); + operation.ProfileName = null; + ToolControl.BaseOperation = operation; + SelectLayers(operation.LayerRangeSelection); + + if (operation is OperationMorph operationMorph && ToolControl is ToolMorphControl toolMorphControl) + { + toolMorphControl.MorphSelectedIndex = operationMorph.MorphOperationIndex; + } + else if (operation is OperationBlur operationBlur && ToolControl is ToolBlurControl toolBlurControl) + { + toolBlurControl.SelectedAlgorithmIndex = operationBlur.BlurTypeIndex; + } + } + } + + public string ProfileText + { + get => _profileText; + set => RaiseAndSetIfChanged(ref _profileText, value); + } + + public async void AddProfile() + { + var name = string.IsNullOrWhiteSpace(_profileText) ? null : _profileText.Trim(); + var operation = OperationProfiles.FindByName(ToolControl.BaseOperation, name); + if (!(operation is null)) + { + if (await this.MessageBoxQuestion( + $"A profile with same name or settings already exists.\nDo you want to overwrite:\n{operation}", + "Overwrite profile?") != ButtonResult.Yes) return; + /*var index = OperationProfiles.Instance.IndexOf(operation); + OperationProfiles.Profiles[index] = ToolControl.BaseOperation; + index = Profiles.IndexOf(operation); + Profiles[index] = ToolControl.BaseOperation;*/ + + OperationProfiles.RemoveProfile(operation, false); + Profiles.Remove(operation); + } + + var toAdd = ToolControl.BaseOperation.Clone(); + toAdd.ProfileName = string.IsNullOrWhiteSpace(_profileText) ? null : _profileText.Trim(); + OperationProfiles.AddProfile(toAdd); + Profiles.Insert(0, toAdd); + + ProfileText = null; + } + + public async void RemoveSelectedProfile() + { + if (_selectedProfileItem is null) return; + if (await this.MessageBoxQuestion( + $"Are you sure you want to remove the selected profile?\n{_selectedProfileItem}", + "Remove selected profile?") != ButtonResult.Yes) return; + + OperationProfiles.RemoveProfile(_selectedProfileItem); + Profiles.Remove(_selectedProfileItem); + SelectedProfileItem = null; + } + + public async void ClearProfiles() + { + if (Profiles.Count == 0) return; + if (await this.MessageBoxQuestion( + $"Are you sure you want to clear all the {Profiles.Count} profiles?", + "Clear all profiles?") != ButtonResult.Yes) return; + + OperationProfiles.ClearProfiles(Profiles[0].GetType()); + Profiles.Clear(); + } + + #endregion + #region Content public bool IsContentVisible => ContentControl is null || ContentControl.IsVisible; @@ -300,41 +455,23 @@ namespace UVtools.WPF.Windows toolControl.Margin = new Thickness(15); Title = toolControl.BaseOperation.Title; - LayerRangeVisible = toolControl.BaseOperation.LayerRangeSelection != Enumerations.LayerRangeSelection.None; + LayerRangeVisible = toolControl.BaseOperation.StartLayerRangeSelection != Enumerations.LayerRangeSelection.None; //IsROIVisible = toolControl.BaseOperation.CanROI; ContentControl = toolControl; ButtonOkText = toolControl.BaseOperation.ButtonOkText; ButtonOkVisible = ButtonOkEnabled = toolControl.BaseOperation.HaveAction; - switch (toolControl.BaseOperation.LayerRangeSelection) - { - case Enumerations.LayerRangeSelection.None: - break; - case Enumerations.LayerRangeSelection.All: - SelectAllLayers(); - break; - case Enumerations.LayerRangeSelection.Current: - SelectCurrentLayer(); - break; - case Enumerations.LayerRangeSelection.Bottom: - SelectBottomLayers(); - break; - case Enumerations.LayerRangeSelection.Normal: - SelectNormalLayers(); - break; - case Enumerations.LayerRangeSelection.First: - SelectFirstLayer(); - break; - case Enumerations.LayerRangeSelection.Last: - SelectLastLayer(); - break; - default: - throw new ArgumentOutOfRangeException(); - } + SelectLayers(toolControl.BaseOperation.StartLayerRangeSelection); //RaisePropertyChanged(nameof(IsContentVisible)); //RaisePropertyChanged(nameof(IsROIVisible)); + if (ToolControl.BaseOperation.CanHaveProfiles) + { + var profiles = OperationProfiles.GetOperations(ToolControl.BaseOperation.GetType()); + Profiles.AddRange(profiles); + IsProfilesVisible = true; + } // Ensure the description don't stretch window DispatcherTimer.Run(() =>