- **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
This commit is contained in:
Tiago Conceição
2021-05-08 23:37:59 +01:00
parent ea410468fc
commit dc0e90a613
37 changed files with 1418 additions and 185 deletions
+25 -1
View File
@@ -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
+173 -10
View File
@@ -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
@@ -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
}
}
+34 -8
View File
@@ -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,23 +342,39 @@ 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 Mat GetRoiOrDefault(Mat defaultMat)
{
return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat;
}
public void ClearMasks()
{
MaskPoints = null;
}
public void SetMasksIfEmpty(Point[][] points)
{
if (HaveMask) return;
MaskPoints = points;
}
public Mat GetRoiOrDefault(Mat defaultMat)
{
return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat;
}
public Mat GetMask(Mat mat) => GetMask(_maskPoints, mat);
public Mat GetMask(Point[][] points, Mat 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;
+1 -1
View File
@@ -175,7 +175,7 @@ namespace UVtools.Core.Operations
throw new ArgumentOutOfRangeException();
}
ApplyMask(original, mat);
ApplyMask(original, target);
return true;
}
+1 -1
View File
@@ -152,7 +152,7 @@ namespace UVtools.Core.Operations
CvInvoke.Flip(target, target, FlipTypeOpenCV);
}
ApplyMask(original, mat);
ApplyMask(original, target);
return true;
}
@@ -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()
+1 -1
View File
@@ -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;
}
@@ -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
}
}
@@ -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();
});
@@ -240,7 +240,7 @@ namespace UVtools.Core.Operations
if (modified)
{
ApplyMask(original, fullMat);
ApplyMask(original, fullMatRoi);
SlicerFile[fullMatLayerIndex].LayerMat = fullMat;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -130,7 +130,7 @@ namespace UVtools.Core.Operations
CvInvoke.DrawContours(target, contours, i, EmguExtensions.WhiteByte, -1);
}
ApplyMask(original, mat);
ApplyMask(original, target);
return true;
}
@@ -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;
}
+3 -3
View File
@@ -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
@@ -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;
}
}
+1 -1
View File
@@ -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; }
}
}
+1 -1
View File
@@ -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>
+141
View File
@@ -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;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

+22 -10
View File
@@ -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}}
};
}
}
@@ -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\}}"/>
@@ -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"
>
@@ -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()
@@ -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>
@@ -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);
}
}
}
@@ -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 =>
{
+2 -2
View File
@@ -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)
+13 -56
View File
@@ -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=" | "/>
<TextBlock Text="{Binding SlicerFile.BottomLayerCount, StringFormat=Bottom layers: {0}}"/>
<TextBlock Text=" | Exposures: "/>
<TextBlock Text=" | "/>
<TextBlock Text="{Binding SlicerFile.ExposureRepresentation, StringFormat=Exposure: {0}}"/>
<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.CanUseAnyLiftHeight}" Text=" | "/>
<TextBlock IsVisible="{Binding SlicerFile.CanUseAnyLiftHeight}"
Text="{Binding SlicerFile.LiftRepresentation, StringFormat=Lift: {0}}"/>
<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 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.CanUseRetractSpeed}" Text=" | "/>
<TextBlock IsVisible="{Binding SlicerFile.CanUseRetractSpeed}"
Text="{Binding SlicerFile.RetractRepresentation, StringFormat=Retract: {0}}"/>
<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 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}"
+15 -3
View File
@@ -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)
+12 -18
View File
@@ -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))]
+4 -1
View File
@@ -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>
+38 -1
View File
@@ -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();
+33 -1
View File
@@ -1413,8 +1413,38 @@
</ScrollViewer>
</TabItem>
<TabItem Header="Automations" VerticalContentAlignment="Center">
<TabItem Header="Tools" VerticalContentAlignment="Center">
<ScrollViewer Name="ScrollViewer5">
<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.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"
@@ -1513,6 +1543,8 @@
</ScrollViewer>
</TabItem>
</TabControl>
<Border Grid.Row="1" Classes="FooterActions">
+40 -1
View File
@@ -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}"
+150 -14
View File
@@ -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");
}
}
}
}