Refactor suction cup grouping logic

This commit removes the Parent/Child issue structures that were added to Issue, and instead opts for a more lean approach to determining the layers affected by an operation. Instead of building out the entire parent/child tree for ever single suction cup issue, we know only calculate what we need when we need it.

During a repair operation, we calculate only the suction cup issues that have no overlapping suction cup issue below it. For "delete issue" behavior we only calculate the suction cup issues that overlap the selected on in both directions (following that direction until no more overlaps are found).
This commit is contained in:
tslater2006
2021-09-16 22:40:36 -05:00
parent 12222827b0
commit 400f46202d
4 changed files with 82 additions and 59 deletions
-3
View File
@@ -301,9 +301,6 @@ namespace UVtools.Core
/// </summary>
public Point[][] Contours { get; init; }
public LayerIssue ParentIssue = null;
public List<LayerIssue> ChildIssues = null;
public int PixelsCount
{
get
+1 -16
View File
@@ -1493,22 +1493,7 @@ namespace UVtools.Core
var suctionTrapArea = EmguContours.GetContourArea(trap);
if (suctionTrapArea < minimumSuctionArea) continue;
var rect = CvInvoke.BoundingRectangle(trap[0]);
var newIssue = new LayerIssue(this[layerIndex], LayerIssue.IssueType.SuctionCup, trap.ToArrayOfArray(), rect) { Area = suctionTrapArea };
if (layerIndex < LayerCount - 1 && suctionTraps[layerIndex + 1] is not null)
{
foreach (var issue in result.Where(issue => issue.LayerIndex == layerIndex + 1 && issue.Type == LayerIssue.IssueType.SuctionCup))
{
if (EmguContours.CheckContoursIntersect(new VectorOfVectorOfPoint(issue.Contours), trap))
{
issue.ChildIssues ??= new List<LayerIssue>();
issue.ChildIssues.Add(newIssue);
newIssue.ParentIssue = issue;
}
}
}
AddIssue(newIssue);
AddIssue(new LayerIssue(this[layerIndex], LayerIssue.IssueType.SuctionCup, trap.ToArrayOfArray(), rect) { Area = suctionTrapArea });
}
}
}
@@ -19,6 +19,7 @@ using System.Xml.Serialization;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.PixelEditor;
@@ -409,6 +410,33 @@ namespace UVtools.Core.Operations
if (_repairSuctionCups)
{
/* build out parent/child relationships between all detected suction cups */
ConcurrentBag<LayerIssue> bottomSuctionIssues = new();
var layerCount = SlicerFile.LayerCount;
Parallel.ForEach(Issues.Where(issue => issue.Type == LayerIssue.IssueType.SuctionCup), issue => {
if (issue.LayerIndex == 0)
{
bottomSuctionIssues.Add(issue);
return;
}
bool overlapFound = false;
foreach(var candidate in Issues.Where(candidate => candidate.LayerIndex == issue.LayerIndex - 1 && candidate.Type == LayerIssue.IssueType.SuctionCup))
{
if (EmguContours.CheckContoursIntersect(new VectorOfVectorOfPoint(issue.Contours), new VectorOfVectorOfPoint(candidate.Contours))) {
overlapFound = true;
break;
}
}
if (!overlapFound)
{
bottomSuctionIssues.Add(issue);
}
});
(bool canDrill, Point location) GetDrillLocation(LayerIssue issue, int diameter)
{
var centroid = EmguCV.EmguContour.GetCentroid(new VectorOfPoint(issue.Contours[0]));
@@ -420,7 +448,7 @@ namespace UVtools.Core.Operations
CvInvoke.DrawContours(contourMat, new VectorOfVectorOfPoint(issue.Contours), -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
CvInvoke.Circle(circleCheck, new(centroid.X + inverseOffset.X, centroid.Y + inverseOffset.Y), 5, EmguExtensions.WhiteColor, -1);
CvInvoke.Circle(circleCheck, new(centroid.X + inverseOffset.X, centroid.Y + inverseOffset.Y), diameter, EmguExtensions.WhiteColor, -1);
CvInvoke.BitwiseAnd(circleCheck, contourMat, overlapCheck);
@@ -437,30 +465,19 @@ namespace UVtools.Core.Operations
}
}
ConcurrentBag<PixelOperation> drillOps = new ConcurrentBag<PixelOperation>();
var suctionReliefSize = (int)Math.Max(SlicerFile.PpmmMax * 1, 20);
Parallel.For(LayerIndexStart, LayerIndexEnd, CoreSettings.ParallelOptions, layerIndex =>
List<PixelOperation> drillOps = new List<PixelOperation>();
var suctionReliefSize = (int)Math.Max(SlicerFile.PpmmMax * .5, 15);
/* for each suction cup issue that is an initial layer */
foreach (var issue in bottomSuctionIssues)
{
if (progress.Token.IsCancellationRequested) return;
var layer = SlicerFile[layerIndex];
if (Issues is not null)
var drillPoint = GetDrillLocation(issue, suctionReliefSize);
if (drillPoint.canDrill)
{
/* for each suction cup issue that is an initial layer */
foreach (var issue in Issues.Where(issue => issue.LayerIndex == layerIndex && issue.Type == LayerIssue.IssueType.SuctionCup && (issue.ChildIssues is null)))
{
var drillPoint = GetDrillLocation(issue, suctionReliefSize);
if (drillPoint.canDrill)
{
drillOps.Add(new PixelDrainHole((uint)layerIndex, drillPoint.location, (byte)suctionReliefSize));
}
}
drillOps.Add(new PixelDrainHole(issue.LayerIndex, drillPoint.location, (byte)suctionReliefSize));
}
}
});
SlicerFile.LayerManager.DrawModifications(drillOps.ToList(), progress);
SlicerFile.LayerManager.DrawModifications(drillOps, progress);
}
+43 -19
View File
@@ -19,9 +19,11 @@ using Emgu.CV.Structure;
using Emgu.CV.Util;
using MessageBox.Avalonia.Enums;
using UVtools.Core;
using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.Operations;
using UVtools.WPF.Extensions;
using static UVtools.Core.LayerIssue;
using Brushes = Avalonia.Media.Brushes;
namespace UVtools.WPF
@@ -83,6 +85,25 @@ namespace UVtools.WPF
IssueSelectedIndex++;
}
public List<LayerIssue> GetOverlappingIssues(LayerIssue targetIssue, int indexOffset)
{
List<LayerIssue> retValue = new();
var targetLayerIndex = targetIssue.LayerIndex + indexOffset;
if (targetLayerIndex > SlicerFile.LayerCount - 1 || targetLayerIndex < 0) return retValue;
foreach (var candidate in Issues.Where(candidate => candidate.LayerIndex == targetLayerIndex && candidate.Type == LayerIssue.IssueType.SuctionCup))
{
if (EmguContours.CheckContoursIntersect(new VectorOfVectorOfPoint(targetIssue.Contours), new VectorOfVectorOfPoint(candidate.Contours)))
{
retValue.Add(candidate);
break;
}
}
return retValue;
}
public async void OnClickIssueRemove()
{
if (IssuesGrid.SelectedItems.Count == 0) return;
@@ -209,8 +230,6 @@ namespace UVtools.WPF
issue.Type != LayerIssue.IssueType.EmptyLayer &&
issue.Type != LayerIssue.IssueType.SuctionCup) continue;
issueRemoveList.Add(issue);
if (issue.Type == LayerIssue.IssueType.Island)
{
var nextLayer = issue.Layer.Index + 1;
@@ -221,30 +240,35 @@ namespace UVtools.WPF
if (issue.Type == LayerIssue.IssueType.SuctionCup)
{
var currentIssue = issue.ParentIssue ?? issue;
/* find the parent */
while(currentIssue.ParentIssue != null)
{
currentIssue = currentIssue.ParentIssue;
}
if (issueRemoveList.Contains(issue)) continue;
/* now currentIssue is the top most parent for the range */
Stack<LayerIssue> children = new Stack<LayerIssue>();
children.Push(currentIssue);
while(children.Count > 0)
Stack<LayerIssue> upDirection = new Stack<LayerIssue>();
Stack<LayerIssue> downDirection = new Stack<LayerIssue>();
upDirection.Push(issue);
downDirection.Push(issue);
while (upDirection.Count > 0)
{
var child = children.Pop();
issueRemoveList.Add(child);
if (child.ChildIssues != null)
var current = upDirection.Pop();
foreach(var iss in GetOverlappingIssues(current,1))
{
foreach (var c in child.ChildIssues)
{
children.Push(c);
}
upDirection.Push(iss);
issueRemoveList.Add(iss);
}
}
while(downDirection.Count > 0)
{
var current = downDirection.Pop();
foreach (var iss in GetOverlappingIssues(current, -1))
{
downDirection.Push(iss);
issueRemoveList.Add(iss);
}
}
}
issueRemoveList.Add(issue);
//Issues.Remove(issue);
}