diff options
37 files changed, 1422 insertions, 189 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 67f50f8..1ae2ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Changelog -## /05/2021 - v2.10.0 +## 08/05/2021 - v2.11.0 + +- **Tools:** + - (Add) Pixel Arithmetic + - (Add) Layer arithmetic: Operator $ to perform a absolute difference + - (Add) Allow to save and auto restore operation settings per session (#195) + - (Add) Allow to auto select the print volume ROI + - (Add) Allow to export and import operation settings from files + - (Improvement) Calculator - LightOff delay: Hide the bottom properties or the tab if the file format don't support them (#193) + - (Change) 'Arithmetic' to 'Layer arithmetic' + - (Remove) 'Threshold pixels' + - (Fix) Solidfy was unable to save profiles + - (Fix) A redo operation (Ctrl + Shift + Z) wasn't restoring the settings when a default profile is set +- **Operations:** + - (Fix) Passing a roi mat to `ApplyMask` would cause unwanted results + - (Improvement) Allow pass a full/original size mask to `ApplyMask` +- **Scripting:** + - (Add) an script to create an printable file to clean the VAT (#170) + - (Improvement) Allow to change user input properties outside the initialization + - (Improvement) Auto format numerical input box with the fixed decimal cases +- (Add) Settings: Section 'Tools' +- (Improvement) GUI: The 'Lift, Retract and Light-off' at status bar now only shows for the supported formats +- (Fix) Print time estimation calculation was wrong since v2.9.3 due a lacking of parentheses on the logic + +## 07/05/2021 - v2.10.0 - **Exposure time finder:** - Add a enable option for each feature diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index ac9a755..a8be672 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -774,7 +775,11 @@ namespace UVtools.Core.FileFormats public virtual float BottomExposureTime { get => _bottomExposureTime; - set => RaiseAndSet(ref _bottomExposureTime, value); + set + { + RaiseAndSet(ref _bottomExposureTime, value); + RaisePropertyChanged(nameof(ExposureRepresentation)); + } } /// <summary> @@ -783,7 +788,11 @@ namespace UVtools.Core.FileFormats public virtual float ExposureTime { get => _exposureTime; - set => RaiseAndSet(ref _exposureTime, value); + set + { + RaiseAndSet(ref _exposureTime, value); + RaisePropertyChanged(nameof(ExposureRepresentation)); + } } /// <summary> @@ -792,7 +801,11 @@ namespace UVtools.Core.FileFormats public virtual float BottomLiftHeight { get => _bottomLiftHeight; - set => RaiseAndSet(ref _bottomLiftHeight, value); + set + { + RaiseAndSet(ref _bottomLiftHeight, value); + RaisePropertyChanged(nameof(LiftRepresentation)); + } } /// <summary> @@ -801,7 +814,11 @@ namespace UVtools.Core.FileFormats public virtual float LiftHeight { get => _liftHeight; - set => RaiseAndSet(ref _liftHeight, value); + set + { + RaiseAndSet(ref _liftHeight, value); + RaisePropertyChanged(nameof(LiftRepresentation)); + } } /// <summary> @@ -810,7 +827,11 @@ namespace UVtools.Core.FileFormats public virtual float BottomLiftSpeed { get => _bottomLiftSpeed; - set => RaiseAndSet(ref _bottomLiftSpeed, value); + set + { + RaiseAndSet(ref _bottomLiftSpeed, value); + RaisePropertyChanged(nameof(LiftRepresentation)); + } } /// <summary> @@ -819,7 +840,11 @@ namespace UVtools.Core.FileFormats public virtual float LiftSpeed { get => _liftSpeed; - set => RaiseAndSet(ref _liftSpeed, value); + set + { + RaiseAndSet(ref _liftSpeed, value); + RaisePropertyChanged(nameof(LiftRepresentation)); + } } /// <summary> @@ -828,7 +853,11 @@ namespace UVtools.Core.FileFormats public virtual float RetractSpeed { get => _retractSpeed; - set => RaiseAndSet(ref _retractSpeed, value); + set + { + RaiseAndSet(ref _retractSpeed, value); + RaisePropertyChanged(nameof(RetractRepresentation)); + } } /// <summary> @@ -837,7 +866,11 @@ namespace UVtools.Core.FileFormats public virtual float BottomLightOffDelay { get => _bottomLightOffDelay; - set => RaiseAndSet(ref _bottomLightOffDelay, value); + set + { + RaiseAndSet(ref _bottomLightOffDelay, value); + RaisePropertyChanged(nameof(LightOffDelayRepresentation)); + } } /// <summary> @@ -846,7 +879,11 @@ namespace UVtools.Core.FileFormats public virtual float LightOffDelay { get => _lightOffDelay; - set => RaiseAndSet(ref _lightOffDelay, value); + set + { + RaiseAndSet(ref _lightOffDelay, value); + RaisePropertyChanged(nameof(LightOffDelayRepresentation)); + } } /// <summary> @@ -867,6 +904,132 @@ namespace UVtools.Core.FileFormats set => RaiseAndSet(ref _lightPwm, value); } + public bool CanUseBottomExposureTime => HavePrintParameterModifier(PrintParameterModifier.BottomExposureSeconds); + public bool CanUseExposureTime => HavePrintParameterModifier(PrintParameterModifier.ExposureSeconds); + public bool CanUseAnyExposureTime => CanUseBottomExposureTime || CanUseExposureTime; + + public bool CanUseBottomLiftHeight => HavePrintParameterModifier(PrintParameterModifier.BottomLiftHeight); + public bool CanUseLiftHeight => HavePrintParameterModifier(PrintParameterModifier.LiftHeight); + public bool CanUseAnyLiftHeight => CanUseBottomLiftHeight || CanUseLiftHeight; + + public bool CanUseBottomLiftSpeed => HavePrintParameterModifier(PrintParameterModifier.BottomLiftSpeed); + public bool CanUseLiftSpeed => HavePrintParameterModifier(PrintParameterModifier.LiftHeight); + public bool CanUseAnyLiftSpeed => CanUseBottomLiftSpeed || CanUseLiftSpeed; + + public bool CanUseRetractSpeed => HavePrintParameterModifier(PrintParameterModifier.RetractSpeed); + + public bool CanUseBottomLightOffDelay => HavePrintParameterModifier(PrintParameterModifier.BottomLightOffDelay); + public bool CanUseLightOffDelay => HavePrintParameterModifier(PrintParameterModifier.LightOffDelay); + public bool CanUseAnyLightOffDelay => CanUseBottomLightOffDelay || CanUseLightOffDelay; + + public bool CanUseBottomLightPWM => HavePrintParameterModifier(PrintParameterModifier.BottomLightPWM); + public bool CanUseLightPWM => HavePrintParameterModifier(PrintParameterModifier.LightPWM); + public bool CanUseAnyLightPWM => CanUseBottomLightPWM || CanUseLightPWM; + + public string ExposureRepresentation + { + get + { + var str = string.Empty; + + if (CanUseBottomExposureTime) + { + str += ExposureTime.ToString(CultureInfo.InvariantCulture); + } + if (CanUseExposureTime) + { + if (!string.IsNullOrEmpty(str)) str += '/'; + str += BottomExposureTime.ToString(CultureInfo.InvariantCulture); + } + + if (!string.IsNullOrEmpty(str)) str += 's'; + + return str; + } + } + + public string LiftRepresentation + { + get + { + var str = string.Empty; + + var haveBottomLiftHeight = CanUseBottomLiftHeight; + var haveLiftHeight = CanUseLiftHeight; + + if (!haveBottomLiftHeight && !haveLiftHeight) return str; + + if (haveBottomLiftHeight) + { + str += BottomLiftHeight.ToString(CultureInfo.InvariantCulture); + } + if (haveLiftHeight) + { + if (!string.IsNullOrEmpty(str)) str += '/'; + str += LiftHeight.ToString(CultureInfo.InvariantCulture); + } + + if (string.IsNullOrEmpty(str)) return str; + + str += "mm @ "; + + var haveBottomLiftSpeed = CanUseBottomLiftSpeed; + var haveLiftSpeed = CanUseLiftSpeed; + if (haveBottomLiftSpeed) + { + str += BottomLiftSpeed.ToString(CultureInfo.InvariantCulture); + } + if (haveLiftSpeed) + { + if (haveBottomLiftSpeed) str += '/'; + str += LiftSpeed.ToString(CultureInfo.InvariantCulture); + } + + str += "mm/min"; + + return str; + } + } + + public string RetractRepresentation + { + get + { + var str = string.Empty; + + if (CanUseRetractSpeed) + { + str += RetractSpeed.ToString(CultureInfo.InvariantCulture); + } + + if (!string.IsNullOrEmpty(str)) str += "mm/min"; + + return str; + } + } + + public string LightOffDelayRepresentation + { + get + { + var str = string.Empty; + + if (CanUseBottomLightOffDelay) + { + str += BottomLightOffDelay.ToString(CultureInfo.InvariantCulture); + } + if (CanUseLightOffDelay) + { + if (!string.IsNullOrEmpty(str)) str += '/'; + str += LightOffDelay.ToString(CultureInfo.InvariantCulture); + } + + if (!string.IsNullOrEmpty(str)) str += 's'; + + return str; + } + } + #endregion /// <summary> @@ -915,7 +1078,7 @@ namespace UVtools.Core.FileFormats } var lightOffDelay = layer.CalculateLightOffDelay(); - time += layer.ExposureTime + lightOffDelay > layer.LightOffDelay ? lightOffDelay : layer.LightOffDelay; + time += layer.ExposureTime + (lightOffDelay > layer.LightOffDelay ? lightOffDelay : layer.LightOffDelay); /*if (lightOffDelay >= layer.LightOffDelay) time += lightOffDelay; else diff --git a/UVtools.Core/Managers/OperationSessionManager.cs b/UVtools.Core/Managers/OperationSessionManager.cs new file mode 100644 index 0000000..f62093d --- /dev/null +++ b/UVtools.Core/Managers/OperationSessionManager.cs @@ -0,0 +1,126 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UVtools.Core.Operations; + +namespace UVtools.Core.Managers +{ + public class OperationSessionManager : IList<Operation> + { + #region Settings + + //public static string FilePath; + #endregion + + #region Singleton + + private static Lazy<OperationSessionManager> _instanceHolder = + new(() => new OperationSessionManager()); + + public static OperationSessionManager Instance => _instanceHolder.Value; + + #endregion + + #region Members + + private readonly List<Operation> _operations = new(); + + #endregion + + #region Properties + + + #endregion + + #region Constructor + private OperationSessionManager() + { + } + #endregion + + #region Methods + + public Operation Find(Type type) + { + return this.FirstOrDefault(operation => operation.GetType() == type); + } + + public Operation Find(Operation fromOperation) => Find(fromOperation.GetType()); + + #endregion + + #region List Implementation + public IEnumerator<Operation> GetEnumerator() + { + return _operations.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) _operations).GetEnumerator(); + } + + public void Add(Operation item) + { + if (item is null) return; + _operations.RemoveAll(operation => operation.GetType() == item.GetType()); + _operations.Add(item.Clone()); + } + + 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 bool Remove(Operation item) + { + return _operations.Remove(item); + } + + public int Count => _operations.Count; + + public bool IsReadOnly => ((ICollection<Operation>) _operations).IsReadOnly; + + public int IndexOf(Operation item) + { + return _operations.IndexOf(item); + } + + public void Insert(int index, Operation item) + { + if (item is null) return; + _operations.RemoveAll(operation => operation.GetType() == item.GetType()); + _operations.Insert(index, item.Clone()); + } + + public void RemoveAt(int index) + { + _operations.RemoveAt(index); + } + + public Operation this[int index] + { + get => _operations[index]; + set => _operations[index] = value; + } + #endregion + } +}
\ No newline at end of file diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index 54abe03..56e2d12 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -13,6 +13,7 @@ using Emgu.CV; using Emgu.CV.Util; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; +using UVtools.Core.Managers; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -341,21 +342,37 @@ namespace UVtools.Core.Operations /// </summary> public virtual void InitWithSlicerFile() { } + public void ClearROI() + { + ROI = Rectangle.Empty; + } + + public void ClearROIandMasks() + { + ClearROI(); + ClearMasks(); + } + public void SetROIIfEmpty(Rectangle roi) { if (HaveROI) return; ROI = roi; } - public void SetMasksIfEmpty(Point[][] points) + public Mat GetRoiOrDefault(Mat defaultMat) { - if (HaveMask) return; - MaskPoints = points; + return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat; } - public Mat GetRoiOrDefault(Mat defaultMat) + public void ClearMasks() { - return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat; + MaskPoints = null; + } + + public void SetMasksIfEmpty(Point[][] points) + { + if (HaveMask) return; + MaskPoints = points; } public Mat GetMask(Mat mat) => GetMask(_maskPoints, mat); @@ -379,8 +396,15 @@ namespace UVtools.Core.Operations { resultRoi = GetRoiOrDefault(result); } - resultRoi.CopyTo(originalRoi, mask); - originalRoi.CopyTo(resultRoi); + + if (mask.Size != resultRoi.Size) // Accept a full size mask + { + mask = GetRoiOrDefault(mask); + } + + using var tempMat = originalRoi.Clone(); + resultRoi.CopyTo(tempMat, mask); + tempMat.CopyTo(resultRoi); } /// <summary> @@ -390,7 +414,7 @@ namespace UVtools.Core.Operations /// <param name="result">Result image which will also be modified</param> public void ApplyMask(Mat original, Mat result) { - using var mask = GetMask(result); + using var mask = GetMask(original); ApplyMask(original, result, mask); } @@ -409,6 +433,8 @@ namespace UVtools.Core.Operations if(!string.IsNullOrWhiteSpace(msg)) throw new InvalidOperationException($"{Title} can't execute due some errors:\n{msg}"); } + + progress ??= new OperationProgress(); progress.Reset(ProgressAction, LayerRangeCount); HaveExecuted = true; diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs index c803177..b472d7d 100644 --- a/UVtools.Core/Operations/OperationBlur.cs +++ b/UVtools.Core/Operations/OperationBlur.cs @@ -175,7 +175,7 @@ namespace UVtools.Core.Operations throw new ArgumentOutOfRangeException(); } - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Operations/OperationFlip.cs b/UVtools.Core/Operations/OperationFlip.cs index 2badeca..b9befcc 100644 --- a/UVtools.Core/Operations/OperationFlip.cs +++ b/UVtools.Core/Operations/OperationFlip.cs @@ -152,7 +152,7 @@ namespace UVtools.Core.Operations CvInvoke.Flip(target, target, FlipTypeOpenCV); } - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Operations/OperationArithmetic.cs b/UVtools.Core/Operations/OperationLayerArithmetic.cs index fd29f51..2ea9bc8 100644 --- a/UVtools.Core/Operations/OperationArithmetic.cs +++ b/UVtools.Core/Operations/OperationLayerArithmetic.cs @@ -13,19 +13,18 @@ using System.Threading.Tasks; using System.Xml.Serialization; using Emgu.CV; using UVtools.Core.FileFormats; -using UVtools.Core.Objects; namespace UVtools.Core.Operations { [Serializable] - public class OperationArithmetic : Operation + public class OperationLayerArithmetic : Operation { #region Members private string _sentence; #endregion #region Enums - public enum ArithmeticOperators : byte + public enum LayerArithmeticOperators : byte { None, Add, @@ -34,7 +33,8 @@ namespace UVtools.Core.Operations Divide, BitwiseAnd, BitwiseOr, - BitwiseXor + BitwiseXor, + AbsDiff } #endregion @@ -42,23 +42,24 @@ namespace UVtools.Core.Operations public sealed class ArithmeticOperation { public uint LayerIndex { get; } - public ArithmeticOperators Operator { get; } + public LayerArithmeticOperators Operator { get; } - public ArithmeticOperation(uint layerIndex, ArithmeticOperators arithmeticOperator) + public ArithmeticOperation(uint layerIndex, LayerArithmeticOperators layerArithmeticOperator) { LayerIndex = layerIndex; - Operator = arithmeticOperator; + Operator = layerArithmeticOperator; } } #endregion #region Overrides - public override string Title => "Arithmetic"; + public override string Title => "Layer arithmetic"; public override string Description => - "Perform arithmetic operations over the layers pixels.\n\n" + + "Perform arithmetic operations over the layers\n" + "Available operators:\n" + " + - * / = Add, Subtract, Multiply, Divide\n" + - " & | ^ = Bitwise AND, OR, XOR\n\n" + + " & | ^ = Bitwise AND, OR, XOR\n" + + " $ = Absolute difference\n\n" + "Syntax: <set_to_layer_indexes> = <layer_index> <operator> <layer_index>\n" + "When: \"<set_to_layer_indexes> =\" is omitted, the result will assign to the first layer on the sentence.\n\n" + "Example 1: 10+11\n" + @@ -115,9 +116,9 @@ namespace UVtools.Core.Operations #region Constructor - public OperationArithmetic() { } + public OperationLayerArithmetic() { } - public OperationArithmetic(FileFormat slicerFile) : base(slicerFile) { } + public OperationLayerArithmetic(FileFormat slicerFile) : base(slicerFile) { } #endregion @@ -155,33 +156,36 @@ namespace UVtools.Core.Operations continue; } - ArithmeticOperators op = ArithmeticOperators.None; + LayerArithmeticOperators op = LayerArithmeticOperators.None; switch (c) { case '+': - op = ArithmeticOperators.Add; + op = LayerArithmeticOperators.Add; break; case '-': - op = ArithmeticOperators.Subtract; + op = LayerArithmeticOperators.Subtract; break; case '*': - op = ArithmeticOperators.Multiply; + op = LayerArithmeticOperators.Multiply; break; case '/': - op = ArithmeticOperators.Divide; + op = LayerArithmeticOperators.Divide; break; case '&': - op = ArithmeticOperators.BitwiseAnd; + op = LayerArithmeticOperators.BitwiseAnd; break; case '|': - op = ArithmeticOperators.BitwiseOr; + op = LayerArithmeticOperators.BitwiseOr; break; case '^': - op = ArithmeticOperators.BitwiseXor; + op = LayerArithmeticOperators.BitwiseXor; + break; + case '$': + op = LayerArithmeticOperators.AbsDiff; break; } - if (op == ArithmeticOperators.None // No valid operator + if (op == LayerArithmeticOperators.None // No valid operator || string.IsNullOrWhiteSpace(layerIndexStr) // Started with a operator instead of layer ) continue; @@ -200,7 +204,7 @@ namespace UVtools.Core.Operations { if (uint.TryParse(layerIndexStr, out var layerIndex)) { - Operations.Add(new ArithmeticOperation(layerIndex, ArithmeticOperators.None)); + Operations.Add(new ArithmeticOperation(layerIndex, LayerArithmeticOperators.None)); } } @@ -227,27 +231,30 @@ namespace UVtools.Core.Operations using var imageMask = GetMask(image); switch (Operations[i - 1].Operator) { - case ArithmeticOperators.Add: + case LayerArithmeticOperators.Add: CvInvoke.Add(resultRoi, imageRoi, resultRoi, imageMask); break; - case ArithmeticOperators.Subtract: + case LayerArithmeticOperators.Subtract: CvInvoke.Subtract(resultRoi, imageRoi, resultRoi, imageMask); break; - case ArithmeticOperators.Multiply: + case LayerArithmeticOperators.Multiply: CvInvoke.Multiply(resultRoi, imageRoi, resultRoi); break; - case ArithmeticOperators.Divide: + case LayerArithmeticOperators.Divide: CvInvoke.Divide(resultRoi, imageRoi, resultRoi); break; - case ArithmeticOperators.BitwiseAnd: + case LayerArithmeticOperators.BitwiseAnd: CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi, imageMask); break; - case ArithmeticOperators.BitwiseOr: + case LayerArithmeticOperators.BitwiseOr: CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi, imageMask); break; - case ArithmeticOperators.BitwiseXor: + case LayerArithmeticOperators.BitwiseXor: CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi, imageMask); break; + case LayerArithmeticOperators.AbsDiff: + CvInvoke.AbsDiff(resultRoi, imageRoi, resultRoi); + break; } } @@ -272,7 +279,7 @@ namespace UVtools.Core.Operations #endregion #region Equality - protected bool Equals(OperationArithmetic other) + protected bool Equals(OperationLayerArithmetic other) { return _sentence == other._sentence; } @@ -282,7 +289,7 @@ namespace UVtools.Core.Operations if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; - return Equals((OperationArithmetic) obj); + return Equals((OperationLayerArithmetic) obj); } public override int GetHashCode() diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs index f2c6804..8c5f561 100644 --- a/UVtools.Core/Operations/OperationMorph.cs +++ b/UVtools.Core/Operations/OperationMorph.cs @@ -183,7 +183,7 @@ namespace UVtools.Core.Operations using var original = mat.Clone(); var target = GetRoiOrDefault(mat); CvInvoke.MorphologyEx(target, target, (MorphOp) MorphOperation, Kernel.Matrix, Kernel.Anchor, iterations, BorderType.Reflect101, default); - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs new file mode 100644 index 0000000..0ce74e8 --- /dev/null +++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs @@ -0,0 +1,362 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * 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 System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; + +namespace UVtools.Core.Operations +{ + [Serializable] + public class OperationPixelArithmetic : Operation + { + #region Members + private PixelArithmeticOperators _operator = PixelArithmeticOperators.Set; + private byte _value = byte.MaxValue; + private ThresholdType _thresholdType = ThresholdType.Binary; + private byte _thresholdMaxValue = 255; + private bool _affectBackPixels; + + #endregion + + #region Enums + public enum PixelArithmeticOperators : byte + { + [Description("Set: to a brightness")] + Set, + [Description("Add: with a brightness")] + Add, + [Description("Subtract: with a brightness")] + Subtract, + [Description("Multiply: with a brightness")] + Multiply, + [Description("Divide: with a brightness")] + Divide, + //[Description("Exponential: pixels by a brightness")] + //Exponential, + [Description("Minimum: set to a brightness if is lower than the current pixel")] + Minimum, + [Description("Maximum: set to a brightness if is higher than the current pixel")] + Maximum, + [Description("Bitwise Not: invert pixels")] + BitwiseNot, + [Description("Bitwise And: with a brightness")] + BitwiseAnd, + [Description("Bitwise Or: with a brightness")] + BitwiseOr, + [Description("Bitwise Xor: with a brightness")] + BitwiseXor, + [Description("AbsDiff: perform a absolute difference between pixel and brightness")] + AbsDiff, + [Description("Threshold: between a minimum/maximum brightness")] + Threshold, + [Description("Keep Region: in the selected ROI or masks")] + KeepRegion, + [Description("Discard Region: in the selected ROI or masks")] + DiscardRegion + } + #endregion + + #region Overrides + public override string Title => "Pixel arithmetic"; + + public override string Description => + "Perform arithmetic operations over the pixels"; + + public override string ConfirmationText => + $"arithmetic {_operator}" + + (ValueEnabled ? $"={_value}" : string.Empty) + + (_operator is PixelArithmeticOperators.Threshold ? $"/{_thresholdMaxValue}" : string.Empty) + + $" layers from {LayerIndexStart} through {LayerIndexEnd}"; + + public override string ProgressTitle => + $"Arithmetic {_operator}"+ + (ValueEnabled ? $"={_value}" : string.Empty) + +$" layers from {LayerIndexStart} through {LayerIndexEnd}"; + + public override string ProgressAction => "Calculated layers"; + + public override string ValidateInternally() + { + var sb = new StringBuilder(); + if (_operator == PixelArithmeticOperators.KeepRegion && !HaveROI && !HaveMask) + { + sb.AppendLine("The 'Keep' operator requires selected ROI/masks."); + } + else if (_operator == PixelArithmeticOperators.DiscardRegion && !HaveROI && !HaveMask) + { + sb.AppendLine("The 'Discard' operator requires selected ROI/masks."); + } + else if (_operator + is PixelArithmeticOperators.Add + or PixelArithmeticOperators.Subtract + or PixelArithmeticOperators.Maximum + or PixelArithmeticOperators.BitwiseOr + or PixelArithmeticOperators.BitwiseXor + or PixelArithmeticOperators.AbsDiff + && _value == 0) + /*|| + (_operator is PixelArithmeticOperators.Exponential && _value == 1) + )*/ + { + sb.AppendLine($"{_operator} by {_value} will have no effect."); + } + else if (_operator == PixelArithmeticOperators.Divide && _value == 0) + { + sb.AppendLine("Can't divide by 0."); + } + + return sb.ToString(); + } + + public override string ToString() + { + var result = $"[{_operator}: {_value}] [ABP: {_affectBackPixels}]" + + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion + + #region Properties + + public PixelArithmeticOperators Operator + { + get => _operator; + set + { + if(!RaiseAndSetIfChanged(ref _operator, value)) return; + RaisePropertyChanged(nameof(ValueEnabled)); + RaisePropertyChanged(nameof(ThresholdEnabled)); + RaisePropertyChanged(nameof(AffectBackPixelsEnabled)); + } + } + + public byte Value + { + get => _value; + set + { + if(!RaiseAndSetIfChanged(ref _value, value)) return; + RaisePropertyChanged(nameof(ValuePercent)); + } + } + + // 255 - 100 + //value - x + public float ValuePercent => (float) Math.Round(_value * 100f / byte.MaxValue, 2); + + public bool ValueEnabled => _operator + is not PixelArithmeticOperators.BitwiseNot + and not PixelArithmeticOperators.KeepRegion + and not PixelArithmeticOperators.DiscardRegion + ; + + public ThresholdType ThresholdType + { + get => _thresholdType; + set => RaiseAndSetIfChanged(ref _thresholdType, value); + } + + public byte ThresholdMaxValue + { + get => _thresholdMaxValue; + set => RaiseAndSetIfChanged(ref _thresholdMaxValue, value); + } + + public bool ThresholdEnabled => _operator is PixelArithmeticOperators.Threshold; + + public bool AffectBackPixels + { + get => _affectBackPixels; + set => RaiseAndSetIfChanged(ref _affectBackPixels, value); + } + + public bool AffectBackPixelsEnabled => _operator + is not PixelArithmeticOperators.Subtract + and not PixelArithmeticOperators.Multiply + and not PixelArithmeticOperators.Divide + and not PixelArithmeticOperators.BitwiseNot + and not PixelArithmeticOperators.BitwiseAnd + and not PixelArithmeticOperators.KeepRegion + and not PixelArithmeticOperators.DiscardRegion + and not PixelArithmeticOperators.Threshold + ; + + #endregion + + #region Constructor + + public OperationPixelArithmetic() { } + + public OperationPixelArithmetic(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + + #region Methods + + protected override bool ExecuteInternally(OperationProgress progress) + { + var tempMat = GetTempMat(); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using (var mat = SlicerFile[layerIndex].LayerMat) + { + Execute(mat, tempMat); + SlicerFile[layerIndex].LayerMat = mat; + } + + progress.LockAndIncrement(); + }); + + tempMat?.Dispose(); + + return !progress.Token.IsCancellationRequested; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + using var original = mat.Clone(); + var target = GetRoiOrDefault(mat); + + Mat tempMat; + bool needDispose = false; + if (arguments is not null && arguments.Length > 0) + { + tempMat = arguments[0] as Mat; + } + else + { + tempMat = GetTempMat(); + needDispose = true; + } + + switch (_operator) + { + case PixelArithmeticOperators.Set: + tempMat.CopyTo(target, _affectBackPixels ? null : target); + break; + case PixelArithmeticOperators.Add: + CvInvoke.Add(target, tempMat, target, _affectBackPixels ? null : target); + break; + case PixelArithmeticOperators.Subtract: + CvInvoke.Subtract(target, tempMat, target, _affectBackPixels ? null : target); + break; + case PixelArithmeticOperators.Multiply: + CvInvoke.Multiply(target, tempMat, target); + break; + case PixelArithmeticOperators.Divide: + CvInvoke.Divide(target, tempMat, target); + break; + /*case PixelArithmeticOperators.Exponential: + CvInvoke.Pow(target, _value, tempMat); + if(!_affectBackPixels) ApplyMask(original, mat, original); + break;*/ + case PixelArithmeticOperators.Minimum: + CvInvoke.Min(target, tempMat, target); + if (!_affectBackPixels) ApplyMask(original, target, original); + break; + case PixelArithmeticOperators.Maximum: + CvInvoke.Max(target, tempMat, target); + if (!_affectBackPixels) ApplyMask(original, target, original); + break; + case PixelArithmeticOperators.BitwiseNot: + CvInvoke.BitwiseNot(target, target); + break; + case PixelArithmeticOperators.BitwiseAnd: + CvInvoke.BitwiseAnd(target, tempMat, target); + break; + case PixelArithmeticOperators.BitwiseOr: + CvInvoke.BitwiseOr(target, tempMat, target, _affectBackPixels ? null : target); + break; + case PixelArithmeticOperators.BitwiseXor: + CvInvoke.BitwiseXor(target, tempMat, target, _affectBackPixels ? null : target); + break; + case PixelArithmeticOperators.Threshold: + CvInvoke.Threshold(target, target, _value, _thresholdMaxValue, _thresholdType); + break; + case PixelArithmeticOperators.AbsDiff: + CvInvoke.AbsDiff(target, tempMat, target); + if (!_affectBackPixels) ApplyMask(original, target, original); + break; + case PixelArithmeticOperators.KeepRegion: + { + using var targetClone = target.Clone(); + original.SetTo(EmguExtensions.BlackByte); + mat.SetTo(EmguExtensions.BlackByte); + targetClone.CopyTo(target); + break; + } + case PixelArithmeticOperators.DiscardRegion: + target.SetTo(EmguExtensions.BlackByte); + break; + default: + throw new NotImplementedException(); + } + + ApplyMask(original, target); + + if (needDispose) + { + tempMat?.Dispose(); + } + + return true; + } + + public Mat GetTempMat() => _operator + is not PixelArithmeticOperators.BitwiseNot + and not PixelArithmeticOperators.KeepRegion + and not PixelArithmeticOperators.DiscardRegion ? EmguExtensions.InitMat(HaveROI ? ROI.Size : SlicerFile.Resolution, new MCvScalar(_value)) : null; + + public void PresetStripAntiAliasing() + { + Operator = PixelArithmeticOperators.Threshold; + Value = 127; + ThresholdMaxValue = 255; + ThresholdType = ThresholdType.Binary; + } + + public void PresetHalfBrightness() + { + Value = 128; + } + + #endregion + + #region Equality + + protected bool Equals(OperationPixelArithmetic other) + { + return _operator == other._operator && _value == other._value && _thresholdType == other._thresholdType && _thresholdMaxValue == other._thresholdMaxValue && _affectBackPixels == other._affectBackPixels; + } + + 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((OperationPixelArithmetic) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine((int) _operator, _value, (int) _thresholdType, _thresholdMaxValue, _affectBackPixels); + } + + #endregion + } +} diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs index ffbfe6e..93b8104 100644 --- a/UVtools.Core/Operations/OperationRaftRelief.cs +++ b/UVtools.Core/Operations/OperationRaftRelief.cs @@ -242,9 +242,9 @@ namespace UVtools.Core.Operations Parallel.For(_ignoreFirstLayers, firstSupportLayerIndex, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var result = SlicerFile[layerIndex].LayerMat; - using var original = result.Clone(); - var target = GetRoiOrDefault(result); + using var mat = SlicerFile[layerIndex].LayerMat; + using var original = mat.Clone(); + var target = GetRoiOrDefault(mat); switch (ReliefType) { @@ -268,8 +268,8 @@ namespace UVtools.Core.Operations break; } - ApplyMask(original, result); - SlicerFile[layerIndex].LayerMat = result; + ApplyMask(original, target); + SlicerFile[layerIndex].LayerMat = mat; progress.LockAndIncrement(); }); diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs index 06f2c77..34b30e4 100644 --- a/UVtools.Core/Operations/OperationRedrawModel.cs +++ b/UVtools.Core/Operations/OperationRedrawModel.cs @@ -240,7 +240,7 @@ namespace UVtools.Core.Operations if (modified) { - ApplyMask(original, fullMat); + ApplyMask(original, fullMatRoi); SlicerFile[fullMatLayerIndex].LayerMat = fullMat; } diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs index 0565437..01e1c79 100644 --- a/UVtools.Core/Operations/OperationResize.cs +++ b/UVtools.Core/Operations/OperationResize.cs @@ -215,7 +215,7 @@ namespace UVtools.Core.Operations using var original = mat.Clone(); var target = GetRoiOrDefault(mat); target.TransformFromCenter((double) xScale, (double) yScale); - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs index 9498246..fe9e215 100644 --- a/UVtools.Core/Operations/OperationRotate.cs +++ b/UVtools.Core/Operations/OperationRotate.cs @@ -101,7 +101,7 @@ namespace UVtools.Core.Operations using var original = mat.Clone(); var target = GetRoiOrDefault(mat); target.Rotate((double)AngleDegrees); - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs index 9a97e1b..ea5e389 100644 --- a/UVtools.Core/Operations/OperationSolidify.cs +++ b/UVtools.Core/Operations/OperationSolidify.cs @@ -130,7 +130,7 @@ namespace UVtools.Core.Operations CvInvoke.DrawContours(target, contours, i, EmguExtensions.WhiteByte, -1); } - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Operations/OperationThreshold.cs b/UVtools.Core/Operations/OperationThreshold.cs index d516b8e..4754f00 100644 --- a/UVtools.Core/Operations/OperationThreshold.cs +++ b/UVtools.Core/Operations/OperationThreshold.cs @@ -101,7 +101,7 @@ namespace UVtools.Core.Operations using var original = mat.Clone(); var target = GetRoiOrDefault(mat); CvInvoke.Threshold(target, target, Threshold, Maximum, Type); - ApplyMask(original, mat); + ApplyMask(original, target); return true; } diff --git a/UVtools.Core/Scripting/ScriptBaseInput.cs b/UVtools.Core/Scripting/ScriptBaseInput.cs index b7f78ff..a7d764c 100644 --- a/UVtools.Core/Scripting/ScriptBaseInput.cs +++ b/UVtools.Core/Scripting/ScriptBaseInput.cs @@ -13,17 +13,17 @@ namespace UVtools.Core.Scripting /// <summary> /// Gets the input label /// </summary> - public string Label { get; init; } + public string Label { get; set; } /// <summary> /// Gets the hover tooltip for this input /// </summary> - public string ToolTip { get; init; } + public string ToolTip { get; set; } /// <summary> /// Gets the value representative unit name /// </summary> - public string Unit { get; init; } + public string Unit { get; set; } } public abstract class ScriptBaseInput<T> : ScriptBaseInput diff --git a/UVtools.Core/Scripting/ScriptNumericalInput.cs b/UVtools.Core/Scripting/ScriptNumericalInput.cs index f894bfa..8ce1a04 100644 --- a/UVtools.Core/Scripting/ScriptNumericalInput.cs +++ b/UVtools.Core/Scripting/ScriptNumericalInput.cs @@ -15,21 +15,21 @@ namespace UVtools.Core.Scripting /// <summary> /// Gets the minimum for this input /// </summary> - public T Minimum { get; init; } + public T Minimum { get; set; } /// <summary> /// Gets the minimum for this input /// </summary> - public T Maximum { get; init; } + public T Maximum { get; set; } /// <summary> /// Gets the increment value for this /// </summary> - public T Increment { get; init; } + public T Increment { get; set; } /// <summary> /// Gets the number of decimal plates to round the value /// </summary> - public byte DecimalPlates { get; init; } = 2; + public byte DecimalPlates { get; set; } = 2; } } diff --git a/UVtools.Core/Scripting/ScriptTextBoxInput.cs b/UVtools.Core/Scripting/ScriptTextBoxInput.cs index 9a31173..ae84e8d 100644 --- a/UVtools.Core/Scripting/ScriptTextBoxInput.cs +++ b/UVtools.Core/Scripting/ScriptTextBoxInput.cs @@ -13,6 +13,6 @@ namespace UVtools.Core.Scripting /// <summary> /// Gets if this input accepts multi lines /// </summary> - public bool MultiLine { get; init; } + public bool MultiLine { get; set; } } } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 72fcd10..f9e1554 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.10.0</Version> + <Version>2.11.0</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> diff --git a/UVtools.ScriptSample/ScriptVATClean.cs b/UVtools.ScriptSample/ScriptVATClean.cs new file mode 100644 index 0000000..aefee4c --- /dev/null +++ b/UVtools.ScriptSample/ScriptVATClean.cs @@ -0,0 +1,141 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * 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 Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.Scripting; + +namespace UVtools.ScriptSample +{ + /// <summary> + /// Change layer properties to random values + /// </summary> + public class ScriptVATClean : ScriptGlobals + { + private ScriptNumericalInput<ushort> InputInset = new() + { + Label = "Resolution inset", + ToolTip = "Inset image resolution by this value to create a black border", + Unit = "px", + Minimum = 0, + Maximum = ushort.MaxValue, + Increment = 1 + }; + + private ScriptNumericalInput<float> InputExposureTime = new() + { + Label = "Exposure time", + ToolTip = "Time to exposure the layer", + Unit = "s", + Minimum = 0, + Maximum = 50, + DecimalPlates = 2, + Increment = 1 + }; + + /// <summary> + /// Set configurations here, this function trigger just after load a script + /// </summary> + public void ScriptInit() + { + Script.Name = "Create a file to clean VAT exposing 1 layer"; + Script.Description = "Print this file to clean your VAT by exposing 1 layer and peel it off.\n" + + "1) Load a file for your printer that you previous printed into UVtools\n" + + "2) Configure and run this script\n" + + "3) Go to File -> Save As, and give it a new name\n" + + "4) Remove head/plate\n" + + "5) Place a plastic spatula in the VAT at an angle with the handle laying on the top of the VAT frame\n" + + "6) Print the created file\n" + + "7) When print finish slowly peel the layer with the spatula"; + Script.Author = "Tiago Conceição"; + Script.Version = new Version(0, 1); + + + InputInset.Maximum = (ushort) (Math.Max(SlicerFile.ResolutionX, SlicerFile.ResolutionY) / 2 - 2); + InputExposureTime.Value = SlicerFile.ExposureTime * 2; + + Script.UserInputs.Add(InputInset); + Script.UserInputs.Add(InputExposureTime); + } + + /// <summary> + /// Validate user inputs here, this function trigger when user click on execute + /// </summary> + /// <returns>A error message, empty or null if validation passes.</returns> + public string ScriptValidate() + { + return null; + } + + /// <summary> + /// Execute the script, this function trigger when when user click on execute and validation passes + /// </summary> + /// <returns>True if executes successfully to the end, otherwise false.</returns> + public bool ScriptExecute() + { + Progress.Reset("Generating layers", 1); // Sets the progress name and number of items to process + + var layer = SlicerFile[0]; + layer.PositionZ = SlicerFile.MachineZ; // Send head to top if possible + + using var mat = EmguExtensions.InitMat(SlicerFile.Resolution); + CvInvoke.Rectangle(mat, new Rectangle( + new Point(InputInset.Value, InputInset.Value), + new Size((int) (SlicerFile.ResolutionX - InputInset.Value*2)-1, (int) (SlicerFile.ResolutionY - InputInset.Value*2)-1) + ), EmguExtensions.WhiteByte, -1, LineType.FourConnected); + layer.LayerMat = mat; + + SlicerFile.SuppressRebuildPropertiesWork(() => + { + SlicerFile.BottomLayerCount = 1; + + SlicerFile.LayerManager.Layers = new[] { layer }; + }); + + SlicerFile.BottomExposureTime = + SlicerFile.ExposureTime = InputExposureTime.Value; + + SlicerFile.BottomLiftSpeed = + SlicerFile.LiftSpeed = + SlicerFile.RetractSpeed = 200; + + SlicerFile.BottomLiftHeight = + SlicerFile.LiftHeight = 1; + + Progress++; + + SlicerFile.SetThumbnails(GetThumbnail()); + + // return true if not cancelled by user + return !Progress.Token.IsCancellationRequested; + } + + public Mat GetThumbnail() + { + Mat thumbnail = EmguExtensions.InitMat(new Size(400, 200), 3); + var fontFace = FontFace.HersheyDuplex; + var fontScale = 1; + var fontThickness = 2; + const byte xSpacing = 45; + const byte ySpacing = 45; + CvInvoke.PutText(thumbnail, "UVtools", new Point(140, 35), fontFace, fontScale, new MCvScalar(255, 27, 245), fontThickness + 1); + CvInvoke.Line(thumbnail, new Point(xSpacing, 0), new Point(xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.Line(thumbnail, new Point(xSpacing, ySpacing + 5), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.Line(thumbnail, new Point(thumbnail.Width - xSpacing, 0), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.PutText(thumbnail, "VAT Clean Utility", new Point(xSpacing, ySpacing * 2), fontFace, fontScale, new MCvScalar(0, 255, 255), fontThickness); + CvInvoke.PutText(thumbnail, $"Exposure time: {SlicerFile.ExposureTime}s", new Point(xSpacing, ySpacing * 3), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + CvInvoke.PutText(thumbnail, $"Use the spatula in!", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + + return thumbnail; + } + } +} diff --git a/UVtools.WPF/Assets/Icons/file-export-16x16.png b/UVtools.WPF/Assets/Icons/file-export-16x16.png Binary files differnew file mode 100644 index 0000000..885e637 --- /dev/null +++ b/UVtools.WPF/Assets/Icons/file-export-16x16.png diff --git a/UVtools.WPF/Controls/Helpers.cs b/UVtools.WPF/Controls/Helpers.cs index ca66103..61046f2 100644 --- a/UVtools.WPF/Controls/Helpers.cs +++ b/UVtools.WPF/Controls/Helpers.cs @@ -15,7 +15,7 @@ namespace UVtools.WPF.Controls { public static readonly List<FileDialogFilter> ImagesFileFilter = new() { - new FileDialogFilter + new() { Name = "Image Files", Extensions = new List<string> @@ -29,9 +29,9 @@ namespace UVtools.WPF.Controls } }; - public static readonly List<FileDialogFilter> PngFileFilter = new List<FileDialogFilter> + public static readonly List<FileDialogFilter> PngFileFilter = new() { - new FileDialogFilter + new() { Name = "Image Files", Extensions = new List<string> @@ -41,9 +41,9 @@ namespace UVtools.WPF.Controls } }; - public static readonly List<FileDialogFilter> TxtFileFilter = new List<FileDialogFilter> + public static readonly List<FileDialogFilter> TxtFileFilter = new() { - new FileDialogFilter + new() { Name = "Text Files", Extensions = new List<string> @@ -53,9 +53,9 @@ namespace UVtools.WPF.Controls } }; - public static readonly List<FileDialogFilter> IniFileFilter = new List<FileDialogFilter> + public static readonly List<FileDialogFilter> IniFileFilter = new() { - new FileDialogFilter + new() { Name = "Ini Files", Extensions = new List<string> @@ -65,9 +65,21 @@ namespace UVtools.WPF.Controls } }; + public static readonly List<FileDialogFilter> OperationSettingFileFilter = new() + { + new() + { + Name = "UVtools operation settings", + Extensions = new List<string> + { + "uvtop", + } + } + }; + public static readonly List<FileDialogFilter> ScriptsFileFilter = new() { - new FileDialogFilter + new() { Name = "Script Files", Extensions = new List<string> @@ -87,9 +99,9 @@ namespace UVtools.WPF.Controls public static List<FileDialogFilter> ToAvaloniaFilter(string name, string extension) { - return new List<FileDialogFilter>(1) + return new(1) { - new FileDialogFilter {Name = name, Extensions = new List<string>(1) {extension}} + new() {Name = name, Extensions = new List<string>(1) {extension}} }; } } diff --git a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml index c179b3b..37a6410 100644 --- a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml @@ -232,7 +232,7 @@ </TabItem> - <TabItem Header="Light-off delay"> + <TabItem Header="Light-off delay" IsVisible="{Binding SlicerFile.CanUseAnyLightOffDelay}"> <Grid RowDefinitions="Auto,10,Auto"> <Border @@ -403,12 +403,16 @@ <TextBlock Grid.Row="0" Grid.Column="6" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" HorizontalAlignment="Right" Text="Bottom lift height:"/> <NumericUpDown Grid.Row="0" Grid.Column="8" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" Minimum="0" Maximum="1000" @@ -418,18 +422,24 @@ <TextBlock Grid.Row="0" Grid.Column="10" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" Text="mm"/> <TextBlock Grid.Row="2" Grid.Column="6" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" HorizontalAlignment="Right" Text="Bottom lift speed:"/> <NumericUpDown Grid.Row="2" Grid.Column="8" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" Minimum="0" Maximum="1000" @@ -439,18 +449,24 @@ <TextBlock Grid.Row="2" Grid.Column="10" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" Text="mm/min"/> <TextBlock Grid.Row="6" Grid.Column="6" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" HorizontalAlignment="Right" Text="Bottom desired wait time:"/> <NumericUpDown Grid.Row="6" Grid.Column="8" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" Minimum="0" Maximum="1000" @@ -460,12 +476,16 @@ <TextBlock Grid.Row="6" Grid.Column="10" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" Text="s"/> <TextBlock Grid.Row="8" Grid.Column="6" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" HorizontalAlignment="Right" FontWeight="Bold" @@ -473,6 +493,8 @@ <TextBox Grid.Row="8" Grid.Column="8" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" IsReadOnly="true" FontWeight="Bold" @@ -480,6 +502,8 @@ <TextBlock Grid.Row="8" Grid.Column="10" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" FontWeight="Bold" VerticalAlignment="Center" Text="s"/> @@ -488,6 +512,8 @@ Grid.Row="10" Grid.Column="6" Grid.ColumnSpan="3" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10" @@ -499,6 +525,8 @@ Grid.Row="12" Grid.Column="6" Grid.ColumnSpan="5" + IsVisible="{Binding SlicerFile.CanUseBottomLightOffDelay}" + IsEnabled="{Binding SlicerFile.CanUseBottomLightOffDelay}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding SlicerFile.BottomLightOffDelay, StringFormat=Current value: \{0\}}"/> diff --git a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerArithmeticControl.axaml index 9239816..87dff5c 100644 --- a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolLayerArithmeticControl.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" - x:Class="UVtools.WPF.Controls.Tools.ToolArithmeticControl" + x:Class="UVtools.WPF.Controls.Tools.ToolLayerArithmeticControl" Width="720" > diff --git a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerArithmeticControl.axaml.cs index 18ad8ff..fc07e66 100644 --- a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerArithmeticControl.axaml.cs @@ -4,14 +4,14 @@ using UVtools.WPF.Windows; namespace UVtools.WPF.Controls.Tools { - public class ToolArithmeticControl : ToolControl + public class ToolLayerArithmeticControl : ToolControl { - public OperationArithmetic Operation => BaseOperation as OperationArithmetic; + public OperationLayerArithmetic Operation => BaseOperation as OperationLayerArithmetic; - public ToolArithmeticControl() + public ToolLayerArithmeticControl() { InitializeComponent(); - BaseOperation = new OperationArithmetic(SlicerFile); + BaseOperation = new OperationLayerArithmetic(SlicerFile); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml new file mode 100644 index 0000000..c4ca28e --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml @@ -0,0 +1,99 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="UVtools.WPF.Controls.Tools.ToolPixelArithmeticControl"> + + <StackPanel Spacing="10"> + <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,Auto,20,Auto,10,Auto,20,Auto,10,Auto"> + + <TextBlock Grid.Row="0" Grid.Column="0" + VerticalAlignment="Center" + Text="Operator:"/> + + <ComboBox Grid.Row="0" Grid.Column="2" + Grid.ColumnSpan="9" + Width="610" + Items="{Binding Operation.Operator, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding Operation.Operator, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/> + + <TextBlock Grid.Row="2" Grid.Column="0" + VerticalAlignment="Center" + IsVisible="{Binding Operation.ValueEnabled}" + IsEnabled="{Binding Operation.ValueEnabled}" + Text="Brightness:"/> + + <StackPanel Grid.Row="2" Grid.Column="2" + VerticalAlignment="Center" + Orientation="Horizontal" Spacing="5" + IsVisible="{Binding Operation.ValueEnabled}" + IsEnabled="{Binding Operation.ValueEnabled}"> + + <NumericUpDown + Minimum="0" + Maximum="255" + Value="{Binding Operation.Value}"/> + + <TextBlock VerticalAlignment="Center" + Text="{Binding Operation.ValuePercent, StringFormat={}{0}%}"/> + + </StackPanel> + + <CheckBox Grid.Row="2" Grid.Column="4" + Content="Affect empty/black pixels" + IsVisible="{Binding Operation.AffectBackPixelsEnabled}" + IsChecked="{Binding Operation.AffectBackPixels}"/> + + <TextBlock Grid.Row="2" Grid.Column="4" + VerticalAlignment="Center" + HorizontalAlignment="Right" + IsVisible="{Binding Operation.ThresholdEnabled}" + IsEnabled="{Binding Operation.ThresholdEnabled}" + Text="Max.:"/> + + <NumericUpDown Grid.Row="2" Grid.Column="6" + Minimum="0" + Maximum="255" + IsVisible="{Binding Operation.ThresholdEnabled}" + IsEnabled="{Binding Operation.ThresholdEnabled}" + Value="{Binding Operation.ThresholdMaxValue}"/> + + <TextBlock Grid.Row="2" Grid.Column="8" + VerticalAlignment="Center" + HorizontalAlignment="Right" + IsVisible="{Binding Operation.ThresholdEnabled}" + IsEnabled="{Binding Operation.ThresholdEnabled}" + Text="Threshold:"/> + + <ComboBox Grid.Row="2" Grid.Column="10" + Width="130" + IsVisible="{Binding Operation.ThresholdEnabled}" + IsEnabled="{Binding Operation.ThresholdEnabled}" + Items="{Binding Operation.ThresholdType, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding Operation.ThresholdType, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/> + + + <TextBlock Grid.Row="4" Grid.Column="0" + VerticalAlignment="Center" + Text="Presets:"/> + + <StackPanel Grid.Row="4" Grid.Column="2" + Grid.ColumnSpan="9" + VerticalAlignment="Center" + Orientation="Horizontal" Spacing="5"> + <Button + Command="{Binding Operation.PresetStripAntiAliasing}" + Content="Strip anti-aliasing"/> + + <Button + IsVisible="{Binding Operation.ValueEnabled}" + Command="{Binding Operation.PresetHalfBrightness}" + Content="Half brightness"/> + </StackPanel> + + </Grid> + </StackPanel> + +</UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml.cs new file mode 100644 index 0000000..eda6fcd --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Markup.Xaml; +using UVtools.Core.Operations; + +namespace UVtools.WPF.Controls.Tools +{ + public partial class ToolPixelArithmeticControl : ToolControl + { + public OperationPixelArithmetic Operation => BaseOperation as OperationPixelArithmetic; + public ToolPixelArithmeticControl() + { + InitializeComponent(); + BaseOperation = new OperationPixelArithmetic(SlicerFile); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs index eaff935..f68e6f2 100644 --- a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs @@ -383,6 +383,11 @@ namespace UVtools.WPF.Controls.Tools MinWidth = 150 }; + if (numFLOAT.DecimalPlates > 0) + { + control.FormatString = $"F{numFLOAT.DecimalPlates}"; + } + var valueProperty = control.GetObservable(NumericUpDown.ValueProperty); valueProperty.Subscribe(value => { @@ -407,6 +412,11 @@ namespace UVtools.WPF.Controls.Tools MinWidth = 150 }; + if (numDOUBLE.DecimalPlates > 0) + { + control.FormatString = $"F{numDOUBLE.DecimalPlates}"; + } + var valueProperty = control.GetObservable(NumericUpDown.ValueProperty); valueProperty.Subscribe(value => { @@ -431,6 +441,11 @@ namespace UVtools.WPF.Controls.Tools MinWidth = 150 }; + if (numDECIMAL.DecimalPlates > 0) + { + control.FormatString = $"F{numDECIMAL.DecimalPlates}"; + } + var valueProperty = control.GetObservable(NumericUpDown.ValueProperty); valueProperty.Subscribe(value => { diff --git a/UVtools.WPF/MainWindow.Clipboard.cs b/UVtools.WPF/MainWindow.Clipboard.cs index bcbf66c..32784c7 100644 --- a/UVtools.WPF/MainWindow.Clipboard.cs +++ b/UVtools.WPF/MainWindow.Clipboard.cs @@ -70,7 +70,7 @@ namespace UVtools.WPF return; } if (clip?.Operation is null) return; - if (clip.Operation.HaveROI) + /*if (clip.Operation.HaveROI) { ROI = GetTransposedRectangle(clip.Operation.ROI); } @@ -78,7 +78,7 @@ namespace UVtools.WPF if (clip.Operation.HaveMask) { AddMaskPoints(clip.Operation.MaskPoints); - } + }*/ var operation = await ShowRunOperation(clip.Operation.GetType(), clip.Operation); if (operation is null) diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 019700e..01577b8 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -284,67 +284,24 @@ VerticalAlignment="Center"> <TextBlock Text="{Binding SlicerFile.LayerHeight, StringFormat=Layer height: \{0\}mm}"/> - <TextBlock Text=" | Bottom layers: "/> - <TextBlock Text="{Binding SlicerFile.BottomLayerCount}"/> - - <TextBlock Text=" | Exposures: "/> - - <TextBlock> - <TextBlock.Text> - <MultiBinding StringFormat="\{0\}s/\{1\}s"> - <Binding Path="SlicerFile.BottomExposureTime"/> - <Binding Path="SlicerFile.ExposureTime"/> - </MultiBinding> - </TextBlock.Text> - </TextBlock> - - <TextBlock IsVisible="{Binding SlicerFile.BottomLiftHeight}" Text=" | Lift: "/> - <TextBlock IsVisible="{Binding SlicerFile.BottomLiftHeight}"> - <TextBlock.Text> - <MultiBinding StringFormat="\{0\}/\{1\}mm @ \{2\}/\{3\}mm/min"> - <Binding Path="SlicerFile.BottomLiftHeight"/> - <Binding Path="SlicerFile.LiftHeight"/> - <Binding Path="SlicerFile.BottomLiftSpeed"/> - <Binding Path="SlicerFile.LiftSpeed"/> - </MultiBinding> - </TextBlock.Text> - </TextBlock> + <TextBlock Text=" | "/> + <TextBlock Text="{Binding SlicerFile.BottomLayerCount, StringFormat=Bottom layers: {0}}"/> - <!-- - <TextBlock IsVisible="{Binding SlicerFile.BottomLiftHeight}" Text="| Bottom lift:"/> - <TextBlock IsVisible="{Binding SlicerFile.BottomLiftHeight}"> - <TextBlock.Text> - <MultiBinding StringFormat="\{0\}mm @ \{1\}mm/min"> - <Binding Path="SlicerFile.BottomLiftHeight"/> - <Binding Path="SlicerFile.BottomLiftSpeed"/> - </MultiBinding> - </TextBlock.Text> - </TextBlock> - - <TextBlock IsVisible="{Binding SlicerFile.LiftHeight}" Text="| Lift:"/> - <TextBlock IsVisible="{Binding SlicerFile.LiftHeight}"> - <TextBlock.Text> - <MultiBinding StringFormat="\{0\}mm @ \{1\}mm/min"> - <Binding Path="SlicerFile.LiftHeight"/> - <Binding Path="SlicerFile.LiftSpeed"/> - </MultiBinding> - </TextBlock.Text> - </TextBlock> - !--> - - <TextBlock IsVisible="{Binding SlicerFile.RetractSpeed}" Text=" | "/> - <TextBlock IsVisible="{Binding SlicerFile.RetractSpeed}" - Text="{Binding SlicerFile.RetractSpeed, StringFormat=Retract Speed: \{0\}mm/min}"/> - - <TextBlock IsVisible="{Binding SlicerFile.LightOffDelay}" Text=" | Light-off: "/> - <TextBlock IsVisible="{Binding SlicerFile.LightOffDelay}"> - <TextBlock.Text> - <MultiBinding StringFormat="\{0\}s/\{1\}s"> - <Binding Path="SlicerFile.BottomLightOffDelay"/> - <Binding Path="SlicerFile.LightOffDelay"/> - </MultiBinding> - </TextBlock.Text> - </TextBlock> + <TextBlock Text=" | "/> + <TextBlock Text="{Binding SlicerFile.ExposureRepresentation, StringFormat=Exposure: {0}}"/> + + <TextBlock IsVisible="{Binding SlicerFile.CanUseAnyLiftHeight}" Text=" | "/> + <TextBlock IsVisible="{Binding SlicerFile.CanUseAnyLiftHeight}" + Text="{Binding SlicerFile.LiftRepresentation, StringFormat=Lift: {0}}"/> + + + <TextBlock IsVisible="{Binding SlicerFile.CanUseRetractSpeed}" Text=" | "/> + <TextBlock IsVisible="{Binding SlicerFile.CanUseRetractSpeed}" + Text="{Binding SlicerFile.RetractRepresentation, StringFormat=Retract: {0}}"/> + + <TextBlock IsVisible="{Binding SlicerFile.CanUseAnyLightOffDelay}" Text=" | "/> + <TextBlock IsVisible="{Binding SlicerFile.CanUseAnyLightOffDelay}" + Text="{Binding SlicerFile.LightOffDelayRepresentation, StringFormat=Light-off: {0}}"/> <TextBlock IsVisible="{Binding SlicerFile.PrintTimeHours}" Text=" | "/> <TextBlock IsVisible="{Binding SlicerFile.PrintTimeHours}" diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 84d074a..fd456c1 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -132,17 +132,25 @@ namespace UVtools.WPF Source = new Bitmap(App.GetAsset("/Assets/Icons/code-branch-16x16.png")) } }, - new() + /*new() { Tag = new OperationThreshold(), Icon = new Avalonia.Controls.Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/th-16x16.png")) } + },*/ + new() + { + Tag = new OperationLayerArithmetic(), + Icon = new Avalonia.Controls.Image + { + Source = new Bitmap(App.GetAsset("/Assets/Icons/square-root-16x16.png")) + } }, new() { - Tag = new OperationArithmetic(), + Tag = new OperationPixelArithmetic(), Icon = new Avalonia.Controls.Image { Source = new Bitmap(App.GetAsset("/Assets/Icons/square-root-16x16.png")) @@ -792,6 +800,9 @@ namespace UVtools.WPF ClearROIAndMask(); + if(!Settings.Tools.LastUsedSettingsKeepOnCloseFile) + OperationSessionManager.Instance.Clear(); + ResetDataContext(); } @@ -1550,7 +1561,8 @@ namespace UVtools.WPF return false; }); - + OperationSessionManager.Instance.Add(baseOperation); + IsGUIEnabled = true; if (result) diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs index ff1e78e..b7fd8e2 100644 --- a/UVtools.WPF/Structures/OperationProfiles.cs +++ b/UVtools.WPF/Structures/OperationProfiles.cs @@ -24,31 +24,25 @@ namespace UVtools.WPF.Structures /// </summary> 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(OperationResize))] [XmlElement(typeof(OperationFlip))] - //[XmlElement(typeof(OperationLayerClone))] - //[XmlElement(typeof(OperationLayerImport))] - [XmlElement(typeof(OperationDynamicLayerHeight))] - [XmlElement(typeof(OperationDynamicLifts))] - //[XmlElement(typeof(OperationLayerReHeight))] - //[XmlElement(typeof(OperationLayerRemove))] - //[XmlElement(typeof(OperationMask))] + [XmlElement(typeof(OperationRotate))] + [XmlElement(typeof(OperationSolidify))] [XmlElement(typeof(OperationMorph))] [XmlElement(typeof(OperationRaftRelief))] [XmlElement(typeof(OperationRedrawModel))] - //[XmlElement(typeof(OperationMove))] - //[XmlElement(typeof(OperationPattern))] + [XmlElement(typeof(OperationThreshold))] + [XmlElement(typeof(OperationLayerArithmetic))] + [XmlElement(typeof(OperationPixelArithmetic))] [XmlElement(typeof(OperationPixelDimming))] [XmlElement(typeof(OperationInfill))] - //[XmlElement(typeof(OperationRepairLayers))] - [XmlElement(typeof(OperationResize))] - [XmlElement(typeof(OperationRotate))] - [XmlElement(typeof(OperationThreshold))] + [XmlElement(typeof(OperationBlur))] + [XmlElement(typeof(OperationDynamicLayerHeight))] + [XmlElement(typeof(OperationDynamicLifts))] + [XmlElement(typeof(OperationChangeResolution))] + [XmlElement(typeof(OperationLayerExportGif))] + [XmlElement(typeof(OperationCalibrateExposureFinder))] [XmlElement(typeof(OperationCalibrateElephantFoot))] [XmlElement(typeof(OperationCalibrateXYZAccuracy))] diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index e0bae71..1793bcc 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ <PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <RepositoryType>Git</RepositoryType> - <Version>2.10.0</Version> + <Version>2.11.0</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -74,6 +74,9 @@ <AvaloniaResource Include="Assets\Icons\*" /> </ItemGroup> <ItemGroup> + <Compile Update="Controls\Tools\ToolLayerArithmeticControl.axaml.cs"> + <DependentUpon>ToolLayerArithmeticControl.axaml</DependentUpon> + </Compile> <Compile Update="Windows\PrusaSlicerManagerWindow.axaml.cs"> <DependentUpon>PrusaSlicerManagerWindow.axaml</DependentUpon> </Compile> diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index 2ce94f2..26fc181 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -22,7 +22,7 @@ namespace UVtools.WPF public sealed class UserSettings : BindableBase { #region Constants - public const ushort SETTINGS_VERSION = 2; + public const ushort SETTINGS_VERSION = 3; #endregion #region Sub classes @@ -1122,6 +1122,36 @@ namespace UVtools.WPF } #endregion + #region Tools + + [Serializable] + public sealed class ToolsUserSettings : BindableBase + { + private bool _restoreLastUsedSettings; + private bool _lastUsedSettingsKeepOnCloseFile = true; + private bool _lastUsedSettingsPriorityOverDefaultProfile = true; + + public bool RestoreLastUsedSettings + { + get => _restoreLastUsedSettings; + set => RaiseAndSetIfChanged(ref _restoreLastUsedSettings, value); + } + + public bool LastUsedSettingsKeepOnCloseFile + { + get => _lastUsedSettingsKeepOnCloseFile; + set => RaiseAndSetIfChanged(ref _lastUsedSettingsKeepOnCloseFile, value); + } + + public bool LastUsedSettingsPriorityOverDefaultProfile + { + get => _lastUsedSettingsPriorityOverDefaultProfile; + set => RaiseAndSetIfChanged(ref _lastUsedSettingsPriorityOverDefaultProfile, value); + } + } + + #endregion + #region Automations [Serializable] @@ -1214,6 +1244,7 @@ namespace UVtools.WPF private IssuesUserSettings _issues; private PixelEditorUserSettings _pixelEditor; private LayerRepairUserSettings _layerRepair; + private ToolsUserSettings _tools; private AutomationsUserSettings _automations; private ushort _settingsVersion = SETTINGS_VERSION; private string _appVersion; @@ -1255,6 +1286,12 @@ namespace UVtools.WPF set => _layerRepair = value; } + public ToolsUserSettings Tools + { + get => _tools ??= new ToolsUserSettings(); + set => _tools = value; + } + public AutomationsUserSettings Automations { get => _automations ??= new AutomationsUserSettings(); diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index a300e3f..c12fcb8 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -1413,7 +1413,7 @@ </ScrollViewer> </TabItem> - <TabItem Header="Automations" VerticalContentAlignment="Center"> + <TabItem Header="Tools" VerticalContentAlignment="Center"> <ScrollViewer Name="ScrollViewer5"> <StackPanel Orientation="Vertical" Spacing="5"> <Border @@ -1423,6 +1423,36 @@ <StackPanel Orientation="Vertical"> <TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="Common"/> <StackPanel Margin="10" Orientation="Vertical" Spacing="10"> + <CheckBox IsChecked="{Binding Settings.Tools.RestoreLastUsedSettings}" + Content="Keep and restore the last used settings on operations per user session/instance"/> + + <ToggleSwitch IsChecked="{Binding Settings.Tools.LastUsedSettingsKeepOnCloseFile}" + OffContent="Discard the session when closing or loading files" + OnContent="Keep the session when closing or loading files"/> + + <ToggleSwitch IsChecked="{Binding Settings.Tools.LastUsedSettingsPriorityOverDefaultProfile}" + IsEnabled="{Binding Settings.Tools.RestoreLastUsedSettings}" + OffContent="Default profile will priority over the session" + OnContent="Session will priority over the default profile"/> + </StackPanel> + + </StackPanel> + </Border> + + </StackPanel> + </ScrollViewer> + </TabItem> + + <TabItem Header="Automations" VerticalContentAlignment="Center"> + <ScrollViewer Name="ScrollViewer6"> + <StackPanel Orientation="Vertical" Spacing="5"> + <Border + Classes="GroupBox" + Margin="5"> + + <StackPanel Orientation="Vertical"> + <TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="Common"/> + <StackPanel Margin="10" Orientation="Vertical" Spacing="10"> <CheckBox IsChecked="{Binding Settings.Automations.SaveFileAfterModifications}" Content="Auto save the file after apply any automation(s)"/> </StackPanel> @@ -1513,6 +1543,8 @@ </ScrollViewer> </TabItem> + + </TabControl> <Border Grid.Row="1" Classes="FooterActions"> diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml index 7e8f766..ff1936d 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml +++ b/UVtools.WPF/Windows/ToolWindow.axaml @@ -365,6 +365,44 @@ <Grid RowDefinitions="Auto" ColumnDefinitions="*"> <StackPanel Spacing="10" Orientation="Horizontal"> + <Button + Command="{Binding #OptionsContextMenu.Open}" + Padding="10" + Content="☰"> + <Button.ContextMenu> + <ContextMenu Name="OptionsContextMenu" PlacementMode="Top"> + <MenuItem + IsVisible="{Binding CanROI}" + Command="{Binding SelectVolumeBoundingRectangle}" + Header="Select print volume ROI"> + <MenuItem.Icon> + <Image Source="/Assets/Icons/expand-16x16.png"/> + </MenuItem.Icon> + </MenuItem> + + <Separator IsVisible="{Binding CanROI}"/> + + <MenuItem + IsVisible="{Binding CanHaveProfiles}" + Command="{Binding ImportSettings}" + Header="Import settings"> + <MenuItem.Icon> + <Image Source="/Assets/Icons/file-import-16x16.png"/> + </MenuItem.Icon> + </MenuItem> + + <MenuItem + IsVisible="{Binding CanHaveProfiles}" + Command="{Binding ExportSettings}" + Header="Export settings"> + <MenuItem.Icon> + <Image Source="/Assets/Icons/file-export-16x16.png"/> + </MenuItem.Icon> + </MenuItem> + </ContextMenu> + </Button.ContextMenu> + </Button> + <Button Padding="10" IsDefault="True" @@ -388,7 +426,8 @@ Spacing="10" HorizontalAlignment="Right" Orientation="Horizontal"> - <Button + + <Button Padding="10" IsDefault="True" IsVisible="{Binding ButtonOkVisible}" diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs index eba5086..de40575 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml.cs +++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs @@ -1,6 +1,8 @@ using System; using System.Collections.ObjectModel; using System.Drawing; +using System.IO; +using System.Xml.Serialization; using Avalonia; using Avalonia.Controls; using Avalonia.Input; @@ -9,12 +11,13 @@ using Avalonia.Threading; using MessageBox.Avalonia.Enums; using UVtools.Core; using UVtools.Core.Extensions; +using UVtools.Core.Managers; using UVtools.Core.Operations; using UVtools.WPF.Controls; using UVtools.WPF.Controls.Tools; using UVtools.WPF.Extensions; using UVtools.WPF.Structures; -using Point = Avalonia.Point; +using Helpers = UVtools.WPF.Controls.Helpers; namespace UVtools.WPF.Windows { @@ -285,7 +288,20 @@ namespace UVtools.WPF.Windows } } - public Rectangle ROI => App.MainWindow.ROI; + public bool CanROI => ToolControl.BaseOperation.CanROI; + + public Rectangle ROI + { + get => App.MainWindow.ROI; + set + { + App.MainWindow.ROI = App.MainWindow.GetTransposedRectangle(value); + ToolControl.BaseOperation.ROI = value; + IsROIVisible = !value.IsEmpty; + RaisePropertyChanged(); + } + } + public System.Drawing.Point[][] Masks => App.MainWindow.MaskPoints?.ToArray(); public bool ClearROIAndMaskAfterOperation @@ -299,8 +315,8 @@ namespace UVtools.WPF.Windows if (await this.MessageBoxQuestion("Are you sure you want to clear the current ROI?\n" + "This action can not be reverted, to select another ROI you must quit this window and select it on layer preview.", "Clear the current ROI?") != ButtonResult.Yes) return; - IsROIVisible = false; - App.MainWindow.ClearROI(); + + ROI = Rectangle.Empty; ToolControl?.Callback(Callbacks.ClearROI); } @@ -311,12 +327,21 @@ namespace UVtools.WPF.Windows "Clear the all masks?") != ButtonResult.Yes) return; IsMasksVisible = false; App.MainWindow.ClearMask(); + ToolControl.BaseOperation.ClearMasks(); ToolControl?.Callback(Callbacks.ClearROI); } + public void SelectVolumeBoundingRectangle() + { + ROI = SlicerFile.BoundingRectangle; + } + #endregion #region Profiles + + public bool CanHaveProfiles => ToolControl.BaseOperation.CanHaveProfiles; + public bool IsProfilesVisible { get => _isProfilesVisible; @@ -565,8 +590,42 @@ namespace UVtools.WPF.Windows _buttonOkText = toolControl.BaseOperation.ButtonOkText; _buttonOkVisible = ButtonOkEnabled = toolControl.BaseOperation.HaveAction; - if (toolControl.BaseOperation.HaveExecuted) // Come from a redo or something + bool fromSession = false; + if (!toolControl.BaseOperation.HaveExecuted && Settings.Tools.RestoreLastUsedSettings) + { + var operation = OperationSessionManager.Instance.Find(toolControl.BaseOperation.GetType()); + if (operation is not null) + { + toolControl.BaseOperation = operation.Clone(); + toolControl.BaseOperation.ClearROIandMasks(); + + switch (operation.LayerRangeSelection) + { + case Enumerations.LayerRangeSelection.None: + LayerIndexStart = operation.LayerIndexStart; + LayerIndexEnd = operation.LayerIndexEnd; + break; + default: + SelectLayers(operation.LayerRangeSelection); + break; + } + + fromSession = true; + } + } + + if (toolControl.BaseOperation.HaveExecuted) // Come from a redo or session { + if (toolControl.BaseOperation.HaveROI) + { + ROI = toolControl.BaseOperation.ROI; + } + + if (toolControl.BaseOperation.HaveMask) + { + App.MainWindow.AddMaskPoints(toolControl.BaseOperation.MaskPoints); + } + LayerIndexStart = toolControl.BaseOperation.LayerIndexStart; LayerIndexEnd = toolControl.BaseOperation.LayerIndexEnd; } @@ -575,25 +634,38 @@ namespace UVtools.WPF.Windows SelectLayers(toolControl.BaseOperation.StartLayerRangeSelection); } - //RaisePropertyChanged(nameof(IsContentVisible)); - //RaisePropertyChanged(nameof(IsROIVisible)); - if (ToolControl.BaseOperation.CanHaveProfiles) { + _isProfilesVisible = true; var profiles = OperationProfiles.GetOperations(ToolControl.BaseOperation.GetType()); - Profiles.AddRange(profiles); - IsProfilesVisible = true; + _profiles.AddRange(profiles); - foreach (var operation in Profiles) + if (!toolControl.BaseOperation.HaveExecuted || + (toolControl.BaseOperation.HaveExecuted && fromSession && !Settings.Tools.LastUsedSettingsPriorityOverDefaultProfile)) { - if (operation.ProfileIsDefault) + //Operation profile = _profiles.FirstOrDefault(operation => operation.ProfileIsDefault); + foreach (var operation in Profiles) { - SelectedProfileItem = operation; - break; + if (operation.ProfileIsDefault) + { + SelectedProfileItem = operation; + break; + } } } } + if (!ReferenceEquals(toolControl.BaseOperation.SlicerFile, SlicerFile)) // Sanitize + { + toolControl.BaseOperation.SlicerFile = SlicerFile; + } + + + //RaisePropertyChanged(nameof(IsContentVisible)); + //RaisePropertyChanged(nameof(IsROIVisible)); + + + // Ensure the description don't stretch window DispatcherTimer.Run(() => { @@ -729,5 +801,69 @@ namespace UVtools.WPF.Windows if (parent is null) return; menu.Open(parent); } + + public async void ExportSettings() + { + if (ToolControl.BaseOperation is null) return; + var dialog = new SaveFileDialog + { + Filters = Helpers.OperationSettingFileFilter, + InitialFileName = ToolControl.BaseOperation.Id + }; + + var file = await dialog.ShowAsync(this); + + if (string.IsNullOrWhiteSpace(file)) return; + + try + { + XmlSerializer serializer = new(ToolControl.BaseOperation.GetType()); + await using StreamWriter writer = new(file); + serializer.Serialize(writer, ToolControl.BaseOperation); + } + catch (Exception e) + { + await this.MessageBoxError(e.ToString(), "Error while trying to export the settings"); + } + } + + public async void ImportSettings() + { + var dialog = new OpenFileDialog + { + AllowMultiple = false, + Filters = Helpers.OperationSettingFileFilter + }; + + var files = await dialog.ShowAsync(this); + + if (files is null || files.Length == 0) return; + + try + { + XmlSerializer serializer = new(ToolControl.BaseOperation.GetType()); + await using var stream = File.OpenRead(files[0]); + var operation = (Operation)serializer.Deserialize(stream); + + operation.SlicerFile = SlicerFile; + ToolControl.BaseOperation = operation; + switch (operation.LayerRangeSelection) + { + case Enumerations.LayerRangeSelection.None: + LayerIndexStart = operation.LayerIndexStart; + LayerIndexEnd = operation.LayerIndexEnd; + break; + default: + SelectLayers(operation.LayerRangeSelection); + break; + } + + ToolControl.Callback(Callbacks.ProfileLoaded); + } + catch (Exception e) + { + await this.MessageBoxError(e.ToString(), "Error while trying to import the settings"); + } + } } } |