mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
2c7ad09dc0
- (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)
409 lines
16 KiB
C#
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
|
|
} |