Files
UVtools/UVtools.Core/Operations/OperationRepairLayers.cs
T
Tiago Conceição 1e9bbf9562 v2.7.2
* **Core:**
   * Fix some improper locks for progress counter and change to Interlocked instead
   * Fix a bug when chaging layer count by remove or add layers it will malform the file after save and crash the program with some tools and/or clipboard
   * Fix when a operation fails by other reason different from cancelation it was not restoring the backup
   * When manually delete/fix issues it will also backup the layers
* **LayerManager:**
   * LayerManager is now readonly and no longer used to transpose layers, each FileFormat have now a unique `LayerManager` instance which is set on the generic constructor
   * Implemented `List<Layer>` methods to easy modify the layers array
   * Changing the `Layers` instance will now recompute some properties, call the properties rebuild and forced sanitize of the structure
   * Better reallocation methods
* **Clipboard Manager:**
   * Add the hability to do full backups, they will be marked with an asterisk (*) at clipboard items list
   * When a partial backup is made and it backups all the layers it will be converted to full backup
   * Clipboard can now restore a snapshot directly with `RestoreSnapshot`
   * Prevent restore the initial backup upon file load and when clearing the clipboard
   * Clip's that change the layer count will perform a full backup and also do a fail-safe backup behind if previous clip is not a full backup
* **Pixel dimming:**
   * Allow to load an image file as a pattern (Do not use very large files or it will take much time to dump the data into the textbox)
   * Empty lines on patterns will be discarded and not trigger validation error
2021-03-28 04:26:01 +01:00

314 lines
12 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.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Objects;
namespace UVtools.Core.Operations
{
[Serializable]
public class OperationRepairLayers : Operation
{
#region Members
private bool _repairIslands = true;
private bool _repairResinTraps = true;
private bool _removeEmptyLayers = true;
private ushort _removeIslandsBelowEqualPixelCount = 5;
private ushort _removeIslandsRecursiveIterations = 4;
private uint _gapClosingIterations = 1;
private uint _noiseRemovalIterations;
#endregion
#region Overrides
public override bool CanROI => false;
public override string Title => "Repair layers and issues";
public override string Description => null;
public override string ConfirmationText => "attempt this repair?";
public override string ProgressTitle =>
$"Reparing layers {LayerIndexStart} through {LayerIndexEnd}";
public override string ProgressAction => "Repaired layers";
public override bool CanHaveProfiles => false;
public override StringTag Validate(params object[] parameters)
{
var sb = new StringBuilder();
if (!RepairIslands && !RemoveEmptyLayers && !RepairResinTraps)
{
sb.AppendLine("You must select at least one repair operation.");
}
return new StringTag(sb.ToString());
}
#endregion
#region Constructor
public OperationRepairLayers() { }
public OperationRepairLayers(FileFormat slicerFile) : base(slicerFile) { }
#endregion
#region Properties
public bool RepairIslands
{
get => _repairIslands;
set => RaiseAndSetIfChanged(ref _repairIslands, value);
}
public bool RepairResinTraps
{
get => _repairResinTraps;
set => RaiseAndSetIfChanged(ref _repairResinTraps, value);
}
public bool RemoveEmptyLayers
{
get => _removeEmptyLayers;
set => RaiseAndSetIfChanged(ref _removeEmptyLayers, value);
}
public ushort RemoveIslandsBelowEqualPixelCount
{
get => _removeIslandsBelowEqualPixelCount;
set => RaiseAndSetIfChanged(ref _removeIslandsBelowEqualPixelCount, value);
}
public ushort RemoveIslandsRecursiveIterations
{
get => _removeIslandsRecursiveIterations;
set => RaiseAndSetIfChanged(ref _removeIslandsRecursiveIterations, value);
}
public uint GapClosingIterations
{
get => _gapClosingIterations;
set => RaiseAndSetIfChanged(ref _gapClosingIterations, value);
}
public uint NoiseRemovalIterations
{
get => _noiseRemovalIterations;
set => RaiseAndSetIfChanged(ref _noiseRemovalIterations, value);
}
[XmlIgnore]
public IslandDetectionConfiguration IslandDetectionConfig { get; set; }
[XmlIgnore]
public List<LayerIssue> Issues { get; set; }
#endregion
#region Methods
protected override bool ExecuteInternally(OperationProgress progress)
{
// Remove islands
if (!ReferenceEquals(Issues, null)
&& !ReferenceEquals(IslandDetectionConfig, null)
&& RepairIslands
&& RemoveIslandsBelowEqualPixelCount > 0
&& RemoveIslandsRecursiveIterations != 1)
{
progress.Reset("Removed recursive islands");
ushort limit = RemoveIslandsRecursiveIterations == 0
? ushort.MaxValue
: RemoveIslandsRecursiveIterations;
var recursiveIssues = Issues;
ConcurrentBag<uint> islandsToRecompute = null;
var islandConfig = IslandDetectionConfig;
var overhangConfig = new OverhangDetectionConfiguration(false);
var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false);
var resinTrapsConfig = new ResinTrapDetectionConfiguration(false);
var emptyLayersConfig = false;
islandConfig.Enabled = true;
islandConfig.RequiredAreaToProcessCheck = (ushort) Math.Floor(RemoveIslandsBelowEqualPixelCount / 2m);
for (uint i = 0; i < limit; i++)
{
if (i > 0)
{
/*var whiteList = islandsToRecompute.GroupBy(u => u)
.Select(grp => grp.First())
.ToList();*/
islandConfig.WhiteListLayers = islandsToRecompute.ToList();
recursiveIssues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, emptyLayersConfig);
//Debug.WriteLine(i);
}
var issuesGroup =
recursiveIssues
.Where(issue => issue.Type == LayerIssue.IssueType.Island &&
issue.Pixels.Length <= RemoveIslandsBelowEqualPixelCount)
.GroupBy(issue => issue.LayerIndex);
if (!issuesGroup.Any()) break; // Nothing to process
islandsToRecompute = new ConcurrentBag<uint>();
Parallel.ForEach(issuesGroup, group =>
{
if (progress.Token.IsCancellationRequested) return;
Layer layer = SlicerFile[group.Key];
Mat image = layer.LayerMat;
Span<byte> bytes = image.GetPixelSpan<byte>();
foreach (var issue in group)
{
foreach (var issuePixel in issue.Pixels)
{
bytes[image.GetPixelPos(issuePixel)] = 0;
}
progress.LockAndIncrement();
}
var nextLayerIndex = group.Key + 1;
if (nextLayerIndex < SlicerFile.LayerCount)
islandsToRecompute.Add(nextLayerIndex);
layer.LayerMat = image;
});
if (islandsToRecompute.IsEmpty) break; // No more leftovers
}
}
progress.Reset(ProgressAction, LayerRangeCount);
if (RepairIslands || RepairResinTraps)
{
Parallel.For(LayerIndexStart, LayerIndexEnd, layerIndex =>
{
if (progress.Token.IsCancellationRequested) return;
Layer layer = SlicerFile[layerIndex];
Mat image = null;
void initImage()
{
image ??= layer.LayerMat;
}
if (Issues is not null)
{
if (RepairIslands && RemoveIslandsBelowEqualPixelCount > 0 && RemoveIslandsRecursiveIterations == 1)
{
Span<byte> bytes = null;
foreach (var issue in Issues)
{
if (
issue.LayerIndex != layerIndex ||
issue.Type != LayerIssue.IssueType.Island ||
issue.Pixels.Length > RemoveIslandsBelowEqualPixelCount) continue;
initImage();
if (bytes == null)
bytes = image.GetPixelSpan<byte>();
foreach (var issuePixel in issue.Pixels)
{
bytes[image.GetPixelPos(issuePixel)] = 0;
}
}
/*if (issues.TryGetValue((uint)layerIndex, out var issueList))
{
var bytes = image.GetPixelSpan<byte>();
foreach (var issue in issueList.Where(issue =>
issue.Type == LayerIssue.IssueType.Island && issue.Pixels.Length <= removeIslandsBelowEqualPixels))
{
foreach (var issuePixel in issue.Pixels)
{
bytes[image.GetPixelPos(issuePixel)] = 0;
}
}
}*/
}
if (RepairResinTraps)
{
foreach (var issue in Issues.Where(issue => issue.LayerIndex == layerIndex && issue.Type == LayerIssue.IssueType.ResinTrap))
{
initImage();
using var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels));
CvInvoke.DrawContours(image,
vec,
-1,
EmguExtensions.WhiteByte,
-1);
}
}
}
if (RepairIslands && (GapClosingIterations > 0 || NoiseRemovalIterations > 0))
{
initImage();
using Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
new Point(-1, -1));
if (GapClosingIterations > 0)
{
CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1),
(int)GapClosingIterations, BorderType.Default, default);
}
if (NoiseRemovalIterations > 0)
{
CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1),
(int)NoiseRemovalIterations, BorderType.Default, default);
}
}
if (!ReferenceEquals(image, null))
{
layer.LayerMat = image;
image.Dispose();
}
progress.LockAndIncrement();
});
}
if (RemoveEmptyLayers)
{
List<uint> removeLayers = new List<uint>();
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
{
if (SlicerFile[layerIndex].NonZeroPixelCount == 0)
{
removeLayers.Add(layerIndex);
}
}
if (removeLayers.Count > 0)
{
OperationLayerRemove.RemoveLayers(SlicerFile, removeLayers, progress);
}
}
return !progress.Token.IsCancellationRequested;
}
#endregion
}
}