Files
UVtools/UVtools.Core/Operations/OperationDoubleExposure.cs
Tiago Conceição 2c7ad09dc0 v3.13.1
- (Change) `Layer.IsBottomLayer` no longer calculate the value using the position of the layer, a new property `IsBottomLayerByHeight` is now used to get that result
- (Improvement) Tool - Double exposure: Increase the bottom layer count per cloned bottom layer
- (Improvement) Calibration - Exposure time finder: Set the absolute bottom layer count accordingly when also testing for bottom time
- (Improvement) Goo: Enforce Wait times or Light-off-delay flag based on property set
- (Fix) AnyCubic and Goo: `PerLayerSetting` flag was set inverted causing printer not to follow layer settings when it should and also the otherwise (#689)
- (Fix) Tool - Scripting: Prevent from reload UI multiple times when using profiles (#694)
2023-04-27 22:58:05 +01:00

409 lines
16 KiB
C#

/*
* 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 Emgu.CV;
using Emgu.CV.CvEnum;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Layers;
using UVtools.Core.Objects;
namespace UVtools.Core.Operations;
public class OperationDoubleExposure : Operation
{
#region Members
private decimal _firstBottomExposure;
private decimal _firstNormalExposure;
private decimal _secondBottomExposure;
private decimal _secondNormalExposure;
private byte _firstBottomErodeIterations = 4;
private byte _secondBottomErodeIterations;
private byte _firstNormalErodeIterations = 1;
private byte _secondNormalErodeIterations;
private bool _secondLayerDifference = true;
private byte _secondLayerDifferenceOverlapErodeIterations = 10;
private bool _differentSettingsForSecondLayer;
private bool _secondLayerLiftHeightEnabled = true;
private decimal _secondLayerLiftHeight;
private bool _secondLayerWaitTimeBeforeCureEnabled = true;
private decimal _secondLayerWaitTimeBeforeCure;
#endregion
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Bottom;
public override string IconClass => "fa-solid fa-grip-lines";
public override string Title => "Double exposure";
public override string Description =>
"The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies.\n" +
"Can be used to eliminate the elephant foot effect or to harden a layer in two steps.\n" +
"After this, do not apply any modification which reconstruct the z positions of the layers.\n" +
"Note: To eliminate the elephant foot effect, the use of wall dimming method is recommended.";
public override string ConfirmationText =>
$"double exposure model layers {LayerIndexStart} through {LayerIndexEnd}";
public override string ProgressTitle =>
$"Double exposure from layers {LayerIndexStart} to {LayerIndexEnd}";
public override string ProgressAction => "Cloned layers";
public override string? ValidateSpawn()
{
if (!SlicerFile.CanUseLayerPositionZ || !SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerExposureTime)
{
return NotSupportedMessage;
}
return null;
}
public override string? ValidateInternally()
{
var sb = new StringBuilder();
//if (LayerRangeHaveBottoms && _firstBottomExposure == _secondBottomExposure && _firstBottomErodeIterations == _secondBottomErodeIterations)
// sb.AppendLine("The settings for bottoms layers will produce exactly to equal layers");
float lastPositionZ = SlicerFile[LayerIndexStart].PositionZ;
for (uint layerIndex = LayerIndexStart + 1; layerIndex <= LayerIndexEnd; layerIndex++)
{
if (lastPositionZ == SlicerFile[layerIndex].PositionZ)
{
sb.AppendLine($"The selected layer range already have modified layers with same z position, starting at layer {layerIndex}. Not safe to continue.");
break;
}
lastPositionZ = SlicerFile[layerIndex].PositionZ;
}
return sb.ToString();
}
public override string ToString()
{
var result = $"[1º exp: {_firstBottomExposure}/{_firstNormalExposure}s erode: {_firstBottomErodeIterations}/{_firstNormalErodeIterations}px] " +
$"[2º exp: {_secondBottomExposure}/{_secondNormalExposure}s erode: {_secondBottomErodeIterations}/{_secondNormalErodeIterations}px] " +
$"[Diff: {_secondLayerDifference} Overlap: {_secondLayerDifferenceOverlapErodeIterations}px]" + LayerRangeString;
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
#endregion
#region Properties
public decimal FirstBottomExposure
{
get => _firstBottomExposure;
set => RaiseAndSetIfChanged(ref _firstBottomExposure, Math.Round(value, 2));
}
public decimal FirstNormalExposure
{
get => _firstNormalExposure;
set => RaiseAndSetIfChanged(ref _firstNormalExposure, Math.Round(value, 2));
}
public decimal SecondBottomExposure
{
get => _secondBottomExposure;
set => RaiseAndSetIfChanged(ref _secondBottomExposure, Math.Round(value, 2));
}
public decimal SecondNormalExposure
{
get => _secondNormalExposure;
set => RaiseAndSetIfChanged(ref _secondNormalExposure, Math.Round(value, 2));
}
public byte FirstBottomErodeIterations
{
get => _firstBottomErodeIterations;
set => RaiseAndSetIfChanged(ref _firstBottomErodeIterations, value);
}
public byte SecondBottomErodeIterations
{
get => _secondBottomErodeIterations;
set => RaiseAndSetIfChanged(ref _secondBottomErodeIterations, value);
}
public byte FirstNormalErodeIterations
{
get => _firstNormalErodeIterations;
set => RaiseAndSetIfChanged(ref _firstNormalErodeIterations, value);
}
public byte SecondNormalErodeIterations
{
get => _secondNormalErodeIterations;
set => RaiseAndSetIfChanged(ref _secondNormalErodeIterations, value);
}
public bool SecondLayerDifference
{
get => _secondLayerDifference;
set => RaiseAndSetIfChanged(ref _secondLayerDifference, value);
}
public byte SecondLayerDifferenceOverlapErodeIterations
{
get => _secondLayerDifferenceOverlapErodeIterations;
set => RaiseAndSetIfChanged(ref _secondLayerDifferenceOverlapErodeIterations, value);
}
public bool DifferentSettingsForSecondLayer
{
get => _differentSettingsForSecondLayer;
set => RaiseAndSetIfChanged(ref _differentSettingsForSecondLayer, value);
}
public bool SecondLayerLiftHeightEnabled
{
get => _secondLayerLiftHeightEnabled;
set => RaiseAndSetIfChanged(ref _secondLayerLiftHeightEnabled, value);
}
public decimal SecondLayerLiftHeight
{
get => _secondLayerLiftHeight;
set => RaiseAndSetIfChanged(ref _secondLayerLiftHeight, value);
}
public bool SecondLayerWaitTimeBeforeCureEnabled
{
get => _secondLayerWaitTimeBeforeCureEnabled;
set => RaiseAndSetIfChanged(ref _secondLayerWaitTimeBeforeCureEnabled, value);
}
public decimal SecondLayerWaitTimeBeforeCure
{
get => _secondLayerWaitTimeBeforeCure;
set => RaiseAndSetIfChanged(ref _secondLayerWaitTimeBeforeCure, value);
}
public KernelConfiguration Kernel { get; set; } = new();
#endregion
#region Constructor
public OperationDoubleExposure() { }
public OperationDoubleExposure(FileFormat slicerFile) : base(slicerFile)
{
if (SlicerFile.SupportPerLayerSettings)
{
_differentSettingsForSecondLayer = true;
if (SlicerFile.SupportsGCode)
{
_secondLayerLiftHeight = 0;
_secondLayerWaitTimeBeforeCure = 2;
}
else
{
_secondLayerLiftHeight = 0.1m;
_secondLayerWaitTimeBeforeCure = 0;
}
}
}
public override void InitWithSlicerFile()
{
base.InitWithSlicerFile();
if (_firstBottomExposure <= 0) _firstBottomExposure = (decimal)SlicerFile.BottomExposureTime;
if (_firstNormalExposure <= 0) _firstNormalExposure = (decimal)SlicerFile.ExposureTime;
if (_secondBottomExposure <= 0) _secondBottomExposure = (decimal)SlicerFile.ExposureTime;
if (_secondNormalExposure <= 0) _secondNormalExposure = (decimal)SlicerFile.ExposureTime;
}
#endregion
#region Equality
protected bool Equals(OperationDoubleExposure other)
{
return _firstBottomExposure == other._firstBottomExposure && _firstNormalExposure == other._firstNormalExposure && _secondBottomExposure == other._secondBottomExposure && _secondNormalExposure == other._secondNormalExposure && _firstBottomErodeIterations == other._firstBottomErodeIterations && _secondBottomErodeIterations == other._secondBottomErodeIterations && _firstNormalErodeIterations == other._firstNormalErodeIterations && _secondNormalErodeIterations == other._secondNormalErodeIterations && _secondLayerDifference == other._secondLayerDifference && _secondLayerDifferenceOverlapErodeIterations == other._secondLayerDifferenceOverlapErodeIterations && _differentSettingsForSecondLayer == other._differentSettingsForSecondLayer && _secondLayerLiftHeightEnabled == other._secondLayerLiftHeightEnabled && _secondLayerLiftHeight == other._secondLayerLiftHeight && _secondLayerWaitTimeBeforeCureEnabled == other._secondLayerWaitTimeBeforeCureEnabled && _secondLayerWaitTimeBeforeCure == other._secondLayerWaitTimeBeforeCure;
}
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((OperationDoubleExposure)obj);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(_firstBottomExposure);
hashCode.Add(_firstNormalExposure);
hashCode.Add(_secondBottomExposure);
hashCode.Add(_secondNormalExposure);
hashCode.Add(_firstBottomErodeIterations);
hashCode.Add(_secondBottomErodeIterations);
hashCode.Add(_firstNormalErodeIterations);
hashCode.Add(_secondNormalErodeIterations);
hashCode.Add(_secondLayerDifference);
hashCode.Add(_secondLayerDifferenceOverlapErodeIterations);
hashCode.Add(_differentSettingsForSecondLayer);
hashCode.Add(_secondLayerLiftHeightEnabled);
hashCode.Add(_secondLayerLiftHeight);
hashCode.Add(_secondLayerWaitTimeBeforeCureEnabled);
hashCode.Add(_secondLayerWaitTimeBeforeCure);
return hashCode.ToHashCode();
}
#endregion
#region Methods
protected override bool ExecuteInternally(OperationProgress progress)
{
var layers = new Layer[SlicerFile.LayerCount+LayerRangeCount];
// Untouched
for (uint i = 0; i < LayerIndexStart; i++)
{
layers[i] = SlicerFile[i];
}
int bottomLayers = SlicerFile.BottomLayerCount;
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
progress.PauseIfRequested();
var firstLayer = SlicerFile[layerIndex];
var secondLayer = firstLayer.Clone();
var isBottomLayer = firstLayer.IsBottomLayer;
if (isBottomLayer)
{
Interlocked.Increment(ref bottomLayers);
}
firstLayer.ExposureTime = (float)( isBottomLayer ? _firstBottomExposure : _firstNormalExposure);
secondLayer.ExposureTime = (float)(isBottomLayer ? _secondBottomExposure : _secondNormalExposure);
if (_differentSettingsForSecondLayer)
{
if (_secondLayerLiftHeightEnabled) secondLayer.LiftHeightTotal = (float)_secondLayerLiftHeight;
if (_secondLayerWaitTimeBeforeCureEnabled) secondLayer.SetWaitTimeBeforeCureOrLightOffDelay((float)_secondLayerWaitTimeBeforeCure);
}
byte firstErodeIterations = isBottomLayer ? _firstBottomErodeIterations : _firstNormalErodeIterations;
byte secondErodeIterations = isBottomLayer ? _secondBottomErodeIterations : _secondNormalErodeIterations;
using (var mat = firstLayer.LayerMat)
{
//using Mat matOriginal = _secondExposureLayerDifference ? mat.Clone() : null;
if (firstErodeIterations > 0 && firstErodeIterations == secondErodeIterations)
{
int tempIterations = firstErodeIterations;
var kernel = Kernel.GetKernel(ref tempIterations);
CvInvoke.Erode(mat, mat, kernel, EmguExtensions.AnchorCenter, tempIterations, BorderType.Reflect101, default);
firstLayer.LayerMat = mat;
firstLayer.CopyImageTo(secondLayer);
if (_secondLayerDifference && _secondLayerDifferenceOverlapErodeIterations > 0)
{
tempIterations = _secondLayerDifferenceOverlapErodeIterations;
kernel = Kernel.GetKernel(ref tempIterations);
using var matErode = new Mat();
CvInvoke.Erode(mat, matErode, kernel, EmguExtensions.AnchorCenter, tempIterations, BorderType.Reflect101, default);
//CvInvoke.Threshold(matErode, matErode, 127, 255, ThresholdType.Binary);
CvInvoke.Subtract(mat, matErode, mat);
secondLayer.LayerMat = mat;
}
else
{
firstLayer.CopyImageTo(secondLayer);
}
}
else
{
Mat? firstMat = null;
Mat? secondMat = null;
if (firstErodeIterations > 0)
{
int tempIterations = firstErodeIterations;
var kernel = Kernel.GetKernel(ref tempIterations);
firstMat = new Mat();
CvInvoke.Erode(mat, firstMat, kernel, EmguExtensions.AnchorCenter, tempIterations, BorderType.Reflect101, default);
firstLayer.LayerMat = firstMat;
}
if (secondErodeIterations > 0)
{
int tempIterations = secondErodeIterations;
var kernel = Kernel.GetKernel(ref tempIterations);
secondMat = new Mat();
CvInvoke.Erode(mat, secondMat, kernel, EmguExtensions.AnchorCenter, tempIterations, BorderType.Reflect101, default);
}
if(firstMat is not null && _secondLayerDifference)
{
if (firstErodeIterations + _secondLayerDifferenceOverlapErodeIterations != secondErodeIterations)
{
if (_secondLayerDifferenceOverlapErodeIterations > 0 &&
firstErodeIterations + _secondLayerDifferenceOverlapErodeIterations != secondErodeIterations)
{
int tempIterations = _secondLayerDifferenceOverlapErodeIterations;
var kernel = Kernel.GetKernel(ref tempIterations);
CvInvoke.Erode(firstMat, firstMat, kernel, EmguExtensions.AnchorCenter, tempIterations, BorderType.Reflect101, default);
//CvInvoke.Threshold(firstMat, firstMat, 127, 255, ThresholdType.Binary);
}
CvInvoke.AbsDiff(firstMat, secondMat ?? mat, mat);
secondLayer.LayerMat = mat;
}
}
else if (secondMat is not null)
{
secondLayer.LayerMat = secondMat;
}
firstMat?.Dispose();
secondMat?.Dispose();
}
}
uint index = LayerIndexStart + (uint)(layerIndex - LayerIndexStart) * 2;
layers[index] = firstLayer;
layers[index + 1] = secondLayer;
progress.LockAndIncrement();
});
// Untouched
for (uint i = LayerIndexEnd+1; i < SlicerFile.LayerCount; i++)
{
layers[i + LayerRangeCount] = SlicerFile[i];
}
SlicerFile.SuppressRebuildPropertiesWork(() =>
{
SlicerFile.BottomLayerCount = (ushort)bottomLayers;
SlicerFile.Layers = layers;
});
return !progress.Token.IsCancellationRequested;
}
#endregion
}