mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-11 19:12:31 +02:00
ada646f92d
- **(Add) PrusaSlicer printers:** - Creality HALOT-MAX CL-133 - Nova3D Elfin2 - Nova3D Elfin2 Mono SE - Nova3D Elfin3 Mini - Nova3D Bene4 - Nova3D Bene5 - Nova3D Whale - Nova3D Whale2 - **About box:** - (Add) Runtime information - (Fix) Limit panels width and height to not overgrow - **(Fix) macOS:** - macOS version auto-upgrade (Will only work on future releases and if running v2.16.0 or greater) - Demo file not loading - Auto disable windows scaling factor when on Monjave or greater on new instalations - (Add) Tool: Raise platform on print finish - (Add) CXDLP: Support for Halot MAX CL-133 - (Improvement) Tools: Better handling/validation of tools that are unable to run with abstraction - (Improvement) CWS: Simplify filenames inside the archive - (Upgrade) EmguCV from 4.5.2 to 4.5.3 - (Change) Allow to set layer `LightPWM` to 0 - (Fix) Arrange dropdown arrow from layer image save icon to be at center
727 lines
29 KiB
C#
727 lines
29 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 System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Serialization;
|
|
using Emgu.CV;
|
|
using Emgu.CV.CvEnum;
|
|
using UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
using UVtools.Core.Objects;
|
|
|
|
namespace UVtools.Core.Operations
|
|
{
|
|
[Serializable]
|
|
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 _displayWidth;
|
|
//private decimal _displayHeight;
|
|
private decimal _minimumLayerHeight = 0.03m;
|
|
private decimal _maximumLayerHeight = 0.10m;
|
|
private decimal _cacheRamSize = 1.5m;
|
|
private ExposureSetTypes _exposureSetType = ExposureSetTypes.Linear;
|
|
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 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.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)Math.Round(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ, Layer.HeightPrecision) ==
|
|
(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 = 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 = $"[Layer Height: Min: {_minimumLayerHeight}mm Max: {_maximumLayerHeight}mm] [RAM: {_cacheRamSize}Gb] [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 DisplayWidth
|
|
{
|
|
get => _displayWidth;
|
|
set
|
|
{
|
|
if (!RaiseAndSetIfChanged(ref _displayWidth, Math.Round(value, 2))) return;
|
|
//RaisePropertyChanged(nameof(XYResolutionUm));
|
|
}
|
|
}
|
|
|
|
public decimal DisplayHeight
|
|
{
|
|
get => _displayHeight;
|
|
set
|
|
{
|
|
if (!RaiseAndSetIfChanged(ref _displayHeight, Math.Round(value, 2))) return;
|
|
//RaisePropertyChanged(nameof(XYResolutionUm));
|
|
}
|
|
}
|
|
|
|
public decimal XYResolutionUm => DisplayWidth > 0 || DisplayHeight > 0 ?
|
|
Math.Round(Math.Max(
|
|
DisplayWidth / SlicerFile.ResolutionX,
|
|
DisplayHeight / SlicerFile.ResolutionY
|
|
), 3) * 1000
|
|
: 0;
|
|
*/
|
|
|
|
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 uint CacheObjectCount => (uint) (_cacheRamSize * 1000000000L / SlicerFile.Resolution.Area() / ObjectsPerCache);
|
|
|
|
public decimal CacheRAMSize
|
|
{
|
|
get => _cacheRamSize;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _cacheRamSize, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(CacheObjectCount));
|
|
}
|
|
}
|
|
|
|
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 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 = (decimal)SlicerFile.BottomExposureTime + count * _bottomExposureStep;
|
|
exposure = (decimal)SlicerFile.ExposureTime + count * _exposureStep;
|
|
break;
|
|
case ExposureSetTypes.Multiplier:
|
|
bottomExposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.BottomExposureTime * count * layerHeight * _bottomExposureStep;
|
|
exposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.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();
|
|
|
|
/*if (SlicerFile.DisplayWidth > 0)
|
|
_displayWidth = (decimal)SlicerFile.DisplayWidth;
|
|
if (SlicerFile.DisplayHeight > 0)
|
|
_displayHeight = (decimal)SlicerFile.DisplayHeight;*/
|
|
|
|
var layerHeight = (decimal)SlicerFile.LayerHeight;
|
|
if (_minimumLayerHeight < layerHeight)
|
|
{
|
|
_minimumLayerHeight = layerHeight;
|
|
}
|
|
if (layerHeight * 2 > _maximumLayerHeight)
|
|
{
|
|
_maximumLayerHeight = Math.Min((decimal) FileFormat.MaximumLayerHeight, _maximumLayerHeight*2);
|
|
}
|
|
}
|
|
|
|
public void InitManualTable()
|
|
{
|
|
for (decimal layerHeight = (decimal)FileFormat.MinimumLayerHeight;
|
|
layerHeight <= (decimal) FileFormat.MaximumLayerHeight;
|
|
layerHeight += (decimal)FileFormat.MinimumLayerHeight)
|
|
{
|
|
var item = new ExposureItem(layerHeight);
|
|
if (layerHeight == (decimal) SlicerFile.LayerHeight)
|
|
{
|
|
item.BottomExposure = (decimal) SlicerFile.BottomExposureTime;
|
|
item.Exposure = (decimal) SlicerFile.ExposureTime;
|
|
}
|
|
_manualExposureTable.Add(item);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Equality
|
|
|
|
private bool Equals(OperationDynamicLayerHeight other)
|
|
{
|
|
return _minimumLayerHeight == other._minimumLayerHeight && _maximumLayerHeight == other._maximumLayerHeight && _cacheRamSize == other._cacheRamSize && _exposureSetType == other._exposureSetType && _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()
|
|
{
|
|
return HashCode.Combine(_minimumLayerHeight, _maximumLayerHeight, _cacheRamSize, (int) _exposureSetType, _bottomExposureStep, _exposureStep, _manualExposureTable);
|
|
}
|
|
|
|
#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 = (decimal)SlicerFile.BottomExposureTime + count * _bottomExposureStep;
|
|
exposure = (decimal)SlicerFile.ExposureTime + count * _exposureStep;
|
|
break;
|
|
case ExposureSetTypes.Multiplier:
|
|
bottomExposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.BottomExposureTime * count * layerHeight * _bottomExposureStep;
|
|
exposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.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++;
|
|
}
|
|
}
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
Report report = new()
|
|
{
|
|
OldLayerCount = SlicerFile.LayerCount,
|
|
OldPrintTime = SlicerFile.PrintTime
|
|
};
|
|
|
|
var anchor = new Point(-1, -1);
|
|
using var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);
|
|
|
|
var matCache = new Mat[SlicerFile.LayerCount];
|
|
var matThresholdCache = new Mat[SlicerFile.LayerCount];
|
|
|
|
List<Layer> layers = new();
|
|
|
|
using Mat matXor = new();
|
|
Mat matXorSum = null;
|
|
Mat matSum = null;
|
|
|
|
const byte _maximumErodes = 10;
|
|
//decimal xyResolutionUm = XYResolutionUm;
|
|
|
|
//const double xyRes = 35;
|
|
//var stepAngle = Math.Atan(SlicerFile.LayerHeight*1000 / xyRes) * (180 / Math.PI);
|
|
//byte maximumErodes = (byte) (_maximumLayerHeight * 100 - (decimal) (slicerFile.LayerHeight * 100f));
|
|
|
|
void CacheLayers(uint fromLayerIndex)
|
|
{
|
|
for (int layerIndex = (int) fromLayerIndex-2; layerIndex >= 0; layerIndex--) // clean up only the required layers
|
|
{
|
|
if (matCache[layerIndex] is null) break;
|
|
matCache[layerIndex].Dispose();
|
|
matCache[layerIndex] = null;
|
|
matThresholdCache[layerIndex].Dispose();
|
|
matThresholdCache[layerIndex] = null;
|
|
}
|
|
Parallel.For(fromLayerIndex, Math.Min(fromLayerIndex + CacheObjectCount, SlicerFile.LayerCount), layerIndex =>
|
|
{
|
|
if (matCache[layerIndex] is not null) return; // Already cached
|
|
matCache[layerIndex] = SlicerFile[layerIndex].LayerMat;
|
|
matThresholdCache[layerIndex] = new Mat();
|
|
|
|
// Clean AA
|
|
CvInvoke.Threshold(matCache[layerIndex], matThresholdCache[layerIndex], 128, 255, ThresholdType.Binary);
|
|
});
|
|
}
|
|
|
|
float GetLastPositionZ(float layerHeight) => layers.Count > 0 ? Layer.RoundHeight(layers[^1].PositionZ + layerHeight) : layerHeight;
|
|
|
|
(Mat, Mat) GetLayer(uint layerIndex)
|
|
{
|
|
if (matCache[layerIndex] is null)
|
|
{
|
|
CacheLayers(layerIndex);
|
|
}
|
|
|
|
return (matCache[layerIndex], matThresholdCache[layerIndex]);
|
|
}
|
|
|
|
void AddNewLayer(Mat mat, float layerHeight)
|
|
{
|
|
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)
|
|
{
|
|
SlicerFile[layerIndex].PositionZ = GetLastPositionZ(SlicerFile.LayerHeight);
|
|
SlicerFile[layerIndex].Index = (uint) layers.Count;
|
|
SlicerFile[layerIndex].IsModified = true;
|
|
layers.Add(SlicerFile[layerIndex]);
|
|
}
|
|
|
|
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)
|
|
{
|
|
progress.Token.ThrowIfCancellationRequested();
|
|
progress.ProcessedItems = layerIndex;
|
|
|
|
if (currentLayerHeight >= (float)_maximumLayerHeight || layerIndex == LayerIndexEnd)
|
|
{
|
|
// Cant perform any additional stack. Maximum layer height reached!
|
|
// Break this cycle and restart from same layer
|
|
matSum ??= GetLayer(layerIndex).Item1.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 (mat1, mat1Threshold) = GetLayer(layerIndex);
|
|
var (mat2, mat2Threshold) = GetLayer(++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.Add(matXorSum, matXor, matXorSum);
|
|
}
|
|
|
|
var currentLayerHeigthUm = currentLayerHeight * 1000.0;
|
|
|
|
if (CvInvoke.CountNonZero(matXorSum) > 0) // Layers are different
|
|
{
|
|
bool meetRequirement = false;
|
|
for (; erodeCount <= _maximumErodes; )
|
|
{
|
|
erodeCount++;
|
|
//maxErodeCount = Math.Max(maxErodeCount, erodeCount);
|
|
|
|
/*var slope = Math.Atan(currentLayerHeigthUm / (double) (xyResolutionUm * erodeCount)) * (180 / Math.PI);
|
|
var stepover = Math.Round(currentLayerHeigthUm / Math.Tan(slope * (Math.PI / 180)));
|
|
Debug.Write($" [Slope: {slope:F2} Stepover: {stepover} <= {xyResolutionUm} = {stepover <= (double) xyResolutionUm}]");
|
|
|
|
if (stepover > (double) xyResolutionUm)
|
|
{
|
|
meetRequirement = false;
|
|
break;
|
|
}*/
|
|
|
|
CvInvoke.Erode(matXorSum, matXor, kernel, anchor, 1, BorderType.Reflect101, default);
|
|
if (CvInvoke.CountNonZero(matXor) == 0) // 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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//erodeCount++; // Safe check
|
|
Debug.Write(" [Equal layer]");
|
|
}
|
|
|
|
layerSum++;
|
|
CvInvoke.Add(matSum, mat2, matSum);
|
|
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 (int i = (int) LayerIndexEnd; i >= 0; i--)
|
|
{
|
|
if(matCache[i] is null) break;
|
|
matCache[i].Dispose();
|
|
matThresholdCache[i].Dispose();
|
|
}
|
|
|
|
for (uint layerIndex = LayerIndexEnd+1; layerIndex < SlicerFile.LayerCount; layerIndex++) // Add left-overs
|
|
{
|
|
ReUseLayer(layerIndex);
|
|
}
|
|
|
|
SlicerFile.SuppressRebuildPropertiesWork(() =>
|
|
{
|
|
SlicerFile.LayerManager.Layers = layers.ToArray();
|
|
}, true, false);
|
|
|
|
// Set exposures times per layer
|
|
var exposureDictionary = ExposureTableDictionary;
|
|
for (uint layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++)
|
|
{
|
|
float bottomExposure = SlicerFile.BottomExposureTime;
|
|
float exposure = SlicerFile.ExposureTime;
|
|
if(exposureDictionary.TryGetValue((decimal)SlicerFile[layerIndex].LayerHeight, out var item))
|
|
{
|
|
bottomExposure = (float) item.BottomExposure;
|
|
exposure = (float) item.Exposure;
|
|
}
|
|
|
|
SlicerFile[layerIndex].ExposureTime = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, 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;
|
|
Tag = report;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|