mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-09 01:52:32 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user