Files
UVtools/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
T
Tiago Conceição a626cfdc72 v3.12.0
- (Add) Allow to pause and resume operations (#654)
- (Add) `Layer.FirstTransitionLayer`
- (Add) `Layer.LastTransitionLayer`
- (Add) File format: Elegoo GOO
- (Add) PrusaSlicer Printer: Elegoo Mars 4
- (Improvement) Allocate maximum GPU memory for Skia up to 256 MB
- (Improvement) Set and sanitize transition layers exposure time from last bottom layer and first normal layer instead of global times (#659)
- (Change) CXDLP: Default version from 2 to 3
- (Fix) UI was not rendering with GPU (ANGLE)
- (Fix) `Layer.IsTransitionLayer` was returning the wrong value
- (Upgrade) .NET from 6.0.13 to 6.0.14
2023-02-27 03:22:40 +00:00

805 lines
30 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Xml.Serialization;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Layers;
using UVtools.Core.Managers;
using UVtools.Core.Objects;
namespace UVtools.Core.Operations;
public sealed class OperationDynamicLayerHeight : Operation
{
#region Sub Classes
public sealed class Report
{
public uint OldLayerCount { get; set; }
public uint NewLayerCount { get; set; }
public uint StackedLayers { get; set; }
public uint ReusedLayers => OldLayerCount - StackedLayers;
public float MaximumLayerHeight { get; set; }
public float OldPrintTime { get; set; }
public float NewPrintTime { get; set; }
public double SparedPrintTime => Math.Round(OldPrintTime - NewPrintTime, 2);
public double CompressionRatio => Math.Round((double)OldLayerCount / NewLayerCount * 100.0, 2);
public override string ToString()
{
var oldTime = TimeSpan.FromSeconds(OldPrintTime);
var newTime = TimeSpan.FromSeconds(NewPrintTime);
var sparedTime = TimeSpan.FromSeconds(SparedPrintTime);
return
$"From {OldLayerCount} layers, {ReusedLayers} got reused, {StackedLayers} got stacked and optimized with dynamic layer height's\n" +
$"Resultant layers: {NewLayerCount}\n" +
$"Compression ratio: {CompressionRatio}%\n" +
$"Maximum layer height reached: {MaximumLayerHeight}mm\n" +
$"Print time: {oldTime.Hours}h{oldTime.Minutes}m{oldTime.Seconds}s -> {newTime.Hours}h{newTime.Minutes}m{newTime.Seconds}s (- {sparedTime.Hours}h{sparedTime.Minutes}m{sparedTime.Seconds}s)";
}
}
#endregion
#region Constants
public const byte ObjectsPerCache = 2;
#endregion
#region Members
private decimal _cacheRamSize = 1.5m;
private decimal _minimumLayerHeight = 0.03m;
private decimal _maximumLayerHeight = 0.10m;
private bool _stripAntiAliasing;
private bool _reconstructAntiAliasing;
private byte _maximumErodes = 10;
private ExposureSetTypes _exposureSetType = ExposureSetTypes.Linear;
private bool _iterateBottomExposureTime;
private decimal _bottomExposureTime;
private decimal _exposureTime;
private decimal _bottomExposureStep = 0.5m;
private decimal _exposureStep = 0.2m;
private RangeObservableCollection<ExposureItem> _automaticExposureTable = new();
private RangeObservableCollection<ExposureItem> _manualExposureTable = new();
#endregion
#region Overrides
public override bool CanROI => false;
public override string IconClass => "mdi-format-line-style";
public override string Title => "Dynamic layer height";
public override string Description =>
"Analyze and optimize the model with dynamic layer heights, larger angles will slice at lower layer height" +
" while more straight angles will slice larger layer height.\n" +
"Note: The model should be sliced at the lowest layer height possible (0.01mm).\n" +
"After this, do not apply any modification which reconstruct the z positions of the layers. " +
"Only few printers support this, make sure your is supported or else it will print a malformed model.";
public override string ConfirmationText =>
$"dynamic layers from layers {LayerIndexStart} through {LayerIndexEnd}?";
public override string ProgressTitle =>
$"Analyzing and optimizing layers height from layers {LayerIndexStart} through {LayerIndexEnd}";
public override string ProgressAction => "Processed layers";
public override string? ValidateSpawn()
{
if (!SlicerFile.CanUseLayerPositionZ || !SlicerFile.CanUseLayerExposureTime)
{
return NotSupportedMessage;
}
if (SlicerFile.LayerHeight * 2 > FileFormat.MaximumLayerHeight)
{
return $"This file already uses the maximum layer height possible ({SlicerFile.LayerHeight}mm).\n" +
"Layers can not be stacked, please re-slice your file with the lowest layer height of 0.01mm.";
}
for (uint layerIndex = 1; layerIndex < SlicerFile.LayerCount; layerIndex++)
{
if ((decimal)Layer.RoundHeight(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ) ==
(decimal)SlicerFile.LayerHeight) continue;
return $"This file contain layer(s) with modified positions, starting at layer {layerIndex}.\n" +
$"This tool requires sequential layers with equal height.\n" +
$"If you run this tool before, you cant re-run.";
}
return null;
}
public override string? ValidateInternally()
{
var sb = new StringBuilder();
/*if (XYResolutionUm <= 0)
{
sb.AppendLine($"Display width and height must be a positive value.");
}*/
decimal layerHeight = (decimal) SlicerFile.LayerHeight;
if (_minimumLayerHeight < layerHeight)
{
sb.AppendLine(
$"Minimum layer height ({_minimumLayerHeight}mm) must be equal or higher than file layer height ({layerHeight}mm)");
}
if (_minimumLayerHeight > _maximumLayerHeight)
{
sb.AppendLine(
$"Minimum layer height ({_minimumLayerHeight}mm) can't be higher than maximum layer height ({_maximumLayerHeight}mm)");
}
if (layerHeight >= _maximumLayerHeight)
{
sb.AppendLine(
$"Maximum layer height ({_maximumLayerHeight}mm) can't be the same or less than current file layer height ({SlicerFile.LayerHeight}mm)");
}
var exposureTable = ExposureTableDictionary;
for (layerHeight = (decimal) SlicerFile.LayerHeight;
layerHeight <= _maximumLayerHeight;
layerHeight += (decimal) SlicerFile.LayerHeight)
{
layerHeight = Layer.RoundHeight(layerHeight);
if (exposureTable.TryGetValue(layerHeight, out var exposure))
{
if (exposure.BottomExposure <= 0 || exposure.Exposure <= 0)
{
sb.AppendLine($"Layer height {layerHeight}mm exposures must be a positive value, current: {exposure.BottomExposure}s/{exposure.Exposure}s");
}
}
else
{
sb.AppendLine($"Layer height {layerHeight}mm exposures are missing.");
}
}
return sb.ToString();
}
public override string ToString()
{
var result = $"[RAM: {_cacheRamSize}Gb] " +
$"[Layer Height: Min: {_minimumLayerHeight}mm Max: {_maximumLayerHeight}mm] " +
$"[Strip AA: {_stripAntiAliasing} Reconstruct AA: {_reconstructAntiAliasing}] " +
$"[Difference: {_maximumErodes}px] " +
$"[Bottom Exposure: {_bottomExposureTime}s Normal Exposure: {_exposureTime}s] " +
$"[Exposure type: {_exposureSetType}, Steps: {_bottomExposureStep}s/{_exposureStep}s]" + LayerRangeString;
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
#endregion
#region Enums
public enum ExposureSetTypes: byte
{
Linear,
Multiplier,
Manual
}
public static Array ExposureSetTypeItems => Enum.GetValues(typeof(ExposureSetTypes));
#endregion
#region Properties
public decimal CacheRAMSize
{
get => _cacheRamSize;
set
{
if (!RaiseAndSetIfChanged(ref _cacheRamSize, Math.Round(value, 2))) return;
RaisePropertyChanged(nameof(CacheObjectCount));
}
}
public uint CacheObjectCount => (uint)(_cacheRamSize * 1000000000L / SlicerFile.Resolution.Area() / ObjectsPerCache);
public decimal MinimumLayerHeight
{
get => _minimumLayerHeight;
set
{
if (!RaiseAndSetIfChanged(ref _minimumLayerHeight, Layer.RoundHeight(value))) return;
//RaisePropertyChanged(nameof(ExposureData));
//if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public decimal MaximumLayerHeight
{
get => _maximumLayerHeight;
set
{
if(!RaiseAndSetIfChanged(ref _maximumLayerHeight, Layer.RoundHeight(value))) return;
//RaisePropertyChanged(nameof(ExposureData));
if(!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public bool StripAntiAliasing
{
get => _stripAntiAliasing;
set => RaiseAndSetIfChanged(ref _stripAntiAliasing, value);
}
public bool ReconstructAntiAliasing
{
get => _reconstructAntiAliasing;
set => RaiseAndSetIfChanged(ref _reconstructAntiAliasing, value);
}
public byte MaximumErodes
{
get => _maximumErodes;
set => RaiseAndSetIfChanged(ref _maximumErodes, value);
}
public ExposureSetTypes ExposureSetType
{
get => _exposureSetType;
set
{
if(!RaiseAndSetIfChanged(ref _exposureSetType, value)) return;
RaisePropertyChanged(nameof(IsExposureSetTypeManual));
RaisePropertyChanged(nameof(ExposureTable));
RaisePropertyChanged(nameof(ExposureTableDictionary));
//RaisePropertyChanged(nameof(ExposureData));
if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public bool IsExposureSetTypeManual => _exposureSetType == ExposureSetTypes.Manual;
public bool IterateBottomExposureTime
{
get => _iterateBottomExposureTime;
set
{
if(!RaiseAndSetIfChanged(ref _iterateBottomExposureTime, value)) return;
if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public decimal BottomExposureTime
{
get => _bottomExposureTime;
set
{
if(!RaiseAndSetIfChanged(ref _bottomExposureTime, value)) return;
if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public decimal ExposureTime
{
get => _exposureTime;
set
{
if(!RaiseAndSetIfChanged(ref _exposureTime, value)) return;
if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public decimal BottomExposureStep
{
get => _bottomExposureStep;
set
{
if(!RaiseAndSetIfChanged(ref _bottomExposureStep, value)) return;
//RaisePropertyChanged(nameof(ExposureData));
if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
public decimal ExposureStep
{
get => _exposureStep;
set
{
if(!RaiseAndSetIfChanged(ref _exposureStep, value)) return;
//RaisePropertyChanged(nameof(ExposureData));
if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
}
[XmlIgnore]
public RangeObservableCollection<ExposureItem> AutomaticExposureTable
{
get
{
if(_automaticExposureTable.Count == 0) RebuildAutoExposureTable();
return _automaticExposureTable;
}
set => RaiseAndSetIfChanged(ref _automaticExposureTable, value);
}
public RangeObservableCollection<ExposureItem> ManualExposureTable
{
get => _manualExposureTable;
set => RaiseAndSetIfChanged(ref _manualExposureTable, value);
}
[XmlIgnore]
public RangeObservableCollection<ExposureItem> ExposureTable => IsExposureSetTypeManual ? _manualExposureTable : AutomaticExposureTable;
/// <summary>
/// Gets the exposure table into a dictionary where key is the layer height
/// </summary>
[XmlIgnore]
public Dictionary<decimal, ExposureItem> ExposureTableDictionary
{
get
{
Dictionary<decimal, ExposureItem> dictionary = new();
foreach (var exposure in ExposureTable)
{
dictionary.TryAdd(exposure.LayerHeight, exposure);
}
return dictionary;
}
}
public string ExposureData
{
get
{
StringBuilder sb = new();
byte count = 0;
for (decimal layerHeight = (decimal) SlicerFile.LayerHeight; layerHeight <= _maximumLayerHeight; layerHeight+= (decimal)SlicerFile.LayerHeight)
{
decimal bottomExposure = 0;
decimal exposure = 0;
switch (_exposureSetType)
{
case ExposureSetTypes.Linear:
bottomExposure = _iterateBottomExposureTime ? _bottomExposureTime + count * _bottomExposureStep : _bottomExposureTime;
exposure = _exposureTime + count * _exposureStep;
break;
case ExposureSetTypes.Multiplier:
bottomExposure = _iterateBottomExposureTime ? _bottomExposureTime + _bottomExposureTime * count * layerHeight * _bottomExposureStep : _bottomExposureTime;
exposure = _exposureTime + _exposureTime * count * layerHeight * _exposureStep;
break;
case ExposureSetTypes.Manual:
break;
default:
throw new ArgumentOutOfRangeException();
}
sb.AppendLine($"{layerHeight:F2}mm: {bottomExposure:F2}s / {exposure:F2}s");
count++;
}
return sb.ToString();
}
}
#endregion
#region Constructor
public OperationDynamicLayerHeight()
{
//InitManualTable();
}
public OperationDynamicLayerHeight(FileFormat slicerFile) : base(slicerFile)
{
InitManualTable();
}
public override void InitWithSlicerFile()
{
base.InitWithSlicerFile();
var layerHeight = (decimal)SlicerFile.LayerHeight;
if (_minimumLayerHeight < layerHeight)
{
_minimumLayerHeight = layerHeight;
}
if (layerHeight * 2 > _maximumLayerHeight)
{
_maximumLayerHeight = Math.Min((decimal) FileFormat.MaximumLayerHeight, _maximumLayerHeight*2);
}
if (_bottomExposureTime <= 0)
_bottomExposureTime = (decimal)SlicerFile.BottomExposureTime;
if (_exposureTime <= 0)
_exposureTime = (decimal)SlicerFile.ExposureTime;
}
public void InitManualTable()
{
for (decimal layerHeight = (decimal)FileFormat.MinimumLayerHeight;
layerHeight <= (decimal) FileFormat.MaximumLayerHeight;
layerHeight += (decimal)FileFormat.MinimumLayerHeight)
{
var item = new ExposureItem(layerHeight, _bottomExposureTime, _exposureTime);
//item.BottomExposure = _bottomExposureTime;
//item.Exposure = _exposureTime;
/*if (layerHeight == (decimal) SlicerFile.LayerHeight)
{
}*/
_manualExposureTable.Add(item);
}
}
#endregion
#region Equality
private bool Equals(OperationDynamicLayerHeight other)
{
return _cacheRamSize == other._cacheRamSize && _minimumLayerHeight == other._minimumLayerHeight && _maximumLayerHeight == other._maximumLayerHeight && _stripAntiAliasing == other._stripAntiAliasing && _reconstructAntiAliasing == other._reconstructAntiAliasing && _maximumErodes == other._maximumErodes && _exposureSetType == other._exposureSetType && _iterateBottomExposureTime == other._iterateBottomExposureTime && _bottomExposureTime == other._bottomExposureTime && _exposureTime == other._exposureTime && _bottomExposureStep == other._bottomExposureStep && _exposureStep == other._exposureStep && Equals(_manualExposureTable, other._manualExposureTable);
}
public override bool Equals(object? obj)
{
return ReferenceEquals(this, obj) || obj is OperationDynamicLayerHeight other && Equals(other);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(_cacheRamSize);
hashCode.Add(_minimumLayerHeight);
hashCode.Add(_maximumLayerHeight);
hashCode.Add(_stripAntiAliasing);
hashCode.Add(_reconstructAntiAliasing);
hashCode.Add(_maximumErodes);
hashCode.Add((int)_exposureSetType);
hashCode.Add(_iterateBottomExposureTime);
hashCode.Add(_bottomExposureTime);
hashCode.Add(_exposureTime);
hashCode.Add(_bottomExposureStep);
hashCode.Add(_exposureStep);
hashCode.Add(_manualExposureTable);
return hashCode.ToHashCode();
}
#endregion
#region Methods
public void RebuildAutoExposureTable()
{
if (SlicerFile is null) return;
_automaticExposureTable.Clear();
byte count = 0;
for (decimal layerHeight = (decimal)SlicerFile.LayerHeight; layerHeight <= _maximumLayerHeight; layerHeight += (decimal)SlicerFile.LayerHeight)
{
decimal bottomExposure = 0;
decimal exposure = 0;
switch (_exposureSetType)
{
case ExposureSetTypes.Linear:
bottomExposure = _iterateBottomExposureTime ? _bottomExposureTime + count * _bottomExposureStep : _bottomExposureTime;
exposure = _exposureTime + count * _exposureStep;
break;
case ExposureSetTypes.Multiplier:
bottomExposure = _iterateBottomExposureTime ? _bottomExposureTime + _bottomExposureTime * count * layerHeight * _bottomExposureStep : _bottomExposureTime;
exposure = _exposureTime + _exposureTime * count * layerHeight * _exposureStep;
break;
case ExposureSetTypes.Manual:
break;
default:
throw new ArgumentOutOfRangeException();
}
_automaticExposureTable.Add(new ExposureItem(layerHeight, Math.Round(bottomExposure, 2), Math.Round(exposure, 2)));
count++;
}
}
public void CopyAutomaticTableToManual()
{
ManualExposureTable.Clear();
ManualExposureTable.AddRange(_automaticExposureTable);
ExposureSetType = ExposureSetTypes.Manual;
}
protected override bool ExecuteInternally(OperationProgress progress)
{
Report report = new()
{
OldLayerCount = SlicerFile.LayerCount,
OldPrintTime = SlicerFile.PrintTime
};
var kernel = EmguExtensions.Kernel3x3Rectangle;
var matCache = new MatCacheManager(this, (ushort)CacheObjectCount, ObjectsPerCache)
{
AutoDispose = true,
AutoDisposeKeepLast = 1,
AfterCacheAction = mats =>
{
mats[1] = new Mat();
// Clean AA
CvInvoke.Threshold(mats[0], mats[1], 127, 255, ThresholdType.Binary);
if (_stripAntiAliasing)
{
mats[0].Dispose();
mats[0] = mats[1];
}
}
};
List<Layer> layers = new();
using Mat matXor = new();
Mat? matXorSum = null;
Mat? matSum = null;
//float xyResolutionUm = SlicerFile.PixelSizeMicronsMax;
//if (xyResolutionUm == 0) xyResolutionUm = 35;
//const double xyRes = 35;
//var stepAngle = Math.Atan(SlicerFile.LayerHeight*1000 / xyRes) * (180 / Math.PI);
//byte maximumErodes = (byte) (_maximumLayerHeight * 100 - (decimal) (SlicerFile.LayerHeight * 100f));
float GetLastPositionZ(float layerHeight) => layers.Count > 0 ? Layer.RoundHeight(layers[^1].PositionZ + layerHeight) : layerHeight;
void AddNewLayer(Mat mat, float layerHeight)
{
if (_stripAntiAliasing && _reconstructAntiAliasing)
{
CvInvoke.GaussianBlur(mat, mat, new Size(3, 3), 0);
}
report.MaximumLayerHeight = Math.Max(report.MaximumLayerHeight, layerHeight);
var positionZ = GetLastPositionZ(layerHeight);
var layer = new Layer((uint) layers.Count, mat, SlicerFile)
{
IsModified = true,
PositionZ = positionZ
};
layers.Add(layer);
}
void ReUseLayer(uint layerIndex)
{
var layer = SlicerFile[layerIndex];
layer.PositionZ = GetLastPositionZ(SlicerFile.LayerHeight);
layer.Index = (uint) layers.Count;
layer.IsModified = true;
if (_stripAntiAliasing)
{
var matThreshold = matCache.Get(layerIndex, 1);
if (_reconstructAntiAliasing)
{
var blurMat = new Mat();
CvInvoke.GaussianBlur(matThreshold, blurMat, new Size(3, 3), 0);
layer.LayerMat = blurMat;
}
else
{
layer.LayerMat = matThreshold;
}
}
layers.Add(layer);
}
for (uint layerIndex = 0; layerIndex < LayerIndexStart; layerIndex++) // Skip layers and re-use layers
{
ReUseLayer(layerIndex);
}
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; )
{
Debug.WriteLine($"Head Layer: {layerIndex} ({SlicerFile.LayerHeight}mm)");
if (layerIndex == LayerIndexEnd)
{
ReUseLayer(layerIndex);
break;
}
var currentLayerHeight = SlicerFile.LayerHeight;
byte layerSum = 1;
byte erodeCount = 0;
//byte maxErodeCount = 0;
//double maxSlop = 90;
matSum?.Dispose();
matSum = null;
matXorSum?.Dispose();
matXorSum = null;
while (true) // In a stack
{
progress.PauseOrCancelIfRequested();
progress.ProcessedItems = layerIndex - LayerIndexStart;
if (currentLayerHeight >= (float)_maximumLayerHeight || layerIndex == LayerIndexEnd)
{
// Cant perform any additional stack. Maximum layer height reached!
// Break this cycle and restart from same layer
matSum ??= matCache.Get(layerIndex, 1).Clone(); // This only happen when layer height is already the maximum supported layer height
layerIndex++;
break;
}
var previousLayerHeight = currentLayerHeight;
currentLayerHeight = Layer.RoundHeight(currentLayerHeight + SlicerFile.LayerHeight);
//var currentLayerHeightUm = currentLayerHeight * 1000;
var (mat1, mat1Threshold) = matCache.Get2(layerIndex);
var (mat2, mat2Threshold) = matCache.Get2(++layerIndex);
Debug.Write($" Stacking layer: {layerIndex} ({currentLayerHeight}mm)");
matSum ??= mat1.Clone();
CvInvoke.BitwiseXor(mat1Threshold, mat2Threshold, matXor);
if (matXorSum is null)
{
matXorSum = matXor.Clone();
}
else
{
CvInvoke.Max(matXorSum, matXor, matXorSum);
}
//var currentLayerHeigthUm = currentLayerHeight * 1000.0;
//CvInvoke.Imshow("test", matXorSum);
//CvInvoke.WaitKey();
if (CvInvoke.CountNonZero(matXorSum) > 0) // Layers are different
//if (!matXorSum.IsZeroed(0, startPos, endPos + 1)) // Layers are different
{
//byte innerErodeCount = 0;
bool meetRequirement = false;
//using var erodeMatXor = matXorSum.Clone();
//Debug.WriteLine($"\n\n{layerIndex} - 0");
//CvInvoke.Imshow("Render", erodeMatXor.Roi(SlicerFile.BoundingRectangle));
//CvInvoke.WaitKey();
while (erodeCount < _maximumErodes)
{
//innerErodeCount++;
erodeCount++;
//maxErodeCount = Math.Max(maxErodeCount, erodeCount);
/*var slope = Math.Atan(currentLayerHeightUm / (double) (xyResolutionUm * erodeCount)) * (180 / Math.PI);
var stepover = Math.Round(currentLayerHeightUm / Math.Tan(slope * (Math.PI / 180)));
Debug.Write($" [Slope: {slope:F2} Stepover: {stepover} <= {xyResolutionUm} = {stepover <= xyResolutionUm}]");
if (stepover > xyResolutionUm)
{
break;
}*/
//Debug.WriteLine($"{layerIndex} - {erodeCount}");
CvInvoke.Erode(matXorSum, matXor, kernel, EmguExtensions.AnchorCenter, 1, BorderType.Reflect101, default);
//CvInvoke.Imshow("Render", erodeMatXor.Roi(SlicerFile.BoundingRectangle));
//CvInvoke.WaitKey();
if (CvInvoke.CountNonZero(matXor) == 0)
//if (erodeMatXor.IsZeroed(0, startPos, endPos+1)) // Image pixels exhausted and got empty image, can pack and go next
{
meetRequirement = true;
break;
}
}
//if ((!meetRequirement || erodeCount >= _maximumErodes) && _minimumLayerHeight < (decimal) currentLayerHeight
// To many pixels, image still not blank, pack the previous group and start again from current height
if (!meetRequirement && _minimumLayerHeight < (decimal) currentLayerHeight)
{
currentLayerHeight = previousLayerHeight;
Debug.WriteLine(string.Empty);
break;
}
if (erodeCount > 0) // Sum only if layers are different from the stack
{
CvInvoke.Max(matSum, mat2, matSum);
}
}
else
{
//erodeCount++; // Safe check
Debug.Write(" [Equal layer]");
}
layerSum++;
Debug.WriteLine(string.Empty);
}
if (layerSum > 1) report.StackedLayers += layerSum;
Debug.WriteLine($" Packing {layerSum} layers with {currentLayerHeight}mm");
// Add the result
var positionZ = GetLastPositionZ(currentLayerHeight);
if ((decimal)positionZ != (decimal)SlicerFile[layerIndex-1].PositionZ)
{
Debug.WriteLine($"{layerIndex}: ({positionZ}mm != {SlicerFile[layerIndex-1].PositionZ}mm) Height mismatch!!");
throw new InvalidOperationException($"Model height integrity has been violated at layer {layerIndex}/{layers.Count} ({positionZ}mm != {SlicerFile[layerIndex - 1].PositionZ}mm), this operation will not proceed.");
}
AddNewLayer(matSum, currentLayerHeight);
}
for (uint layerIndex = LayerIndexEnd+1; layerIndex < SlicerFile.LayerCount; layerIndex++) // Add left-overs
{
ReUseLayer(layerIndex);
}
SlicerFile.SuppressRebuildPropertiesWork(() =>
{
SlicerFile.BottomExposureTime = (float)_bottomExposureTime;
SlicerFile.ExposureTime = (float)_exposureTime;
SlicerFile.Layers = layers.ToArray();
}, true, false);
// Set exposures times per layer
var exposureDictionary = ExposureTableDictionary;
for (uint layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++)
{
var layer = SlicerFile[layerIndex];
var bottomExposure = _bottomExposureTime;
var exposure = _exposureTime;
if(exposureDictionary.TryGetValue((decimal)layer.LayerHeight, out var item))
{
bottomExposure = item.BottomExposure;
exposure = item.Exposure;
}
layer.ExposureTime = (float)SlicerFile.GetBottomOrNormalValue(layer, bottomExposure, exposure);
}
//var layer = slicerFile.LayerManager.Layers[^1];
/*Debug.WriteLine(layer.ExposureTime);
Debug.WriteLine(layer.Index);
Debug.WriteLine(layer.Filename);
Debug.WriteLine(layer.IsNormalLayer);
Debug.WriteLine(layer.IsBottomLayer);
Debug.WriteLine(layer.LiftHeight);
Debug.WriteLine(layer.LiftSpeed);
Debug.WriteLine(layer.LightOffDelay);
Debug.WriteLine(layer.LightPWM);
Debug.WriteLine(layer.BoundingRectangle);*/
//Debug.WriteLine(layer.LayerHeight);
//Debug.WriteLine(layer);
/*Debug.WriteLine(slicerFile.LayerManager);
foreach (var layer in slicerFile)
{
Debug.WriteLine(layer.Index);
}*/
report.NewLayerCount = SlicerFile.LayerCount;
report.NewPrintTime = SlicerFile.PrintTime;
AfterCompleteReport = report.ToString();
return true;
}
#endregion
}