diff --git a/UVtools.Core/EmguCV/EmguContours.cs b/UVtools.Core/EmguCV/EmguContours.cs index 50f6a44..8c0889e 100644 --- a/UVtools.Core/EmguCV/EmguContours.cs +++ b/UVtools.Core/EmguCV/EmguContours.cs @@ -210,7 +210,7 @@ namespace UVtools.Core.EmguCV return result; } - public static bool CheckContoursIntersect(VectorOfVectorOfPoint contour1, VectorOfVectorOfPoint contour2) + public static bool ContoursIntersect(VectorOfVectorOfPoint contour1, VectorOfVectorOfPoint contour2) { var contour1Rect = CvInvoke.BoundingRectangle(contour1[0]); var contour2Rect = CvInvoke.BoundingRectangle(contour2[0]); @@ -226,17 +226,17 @@ namespace UVtools.Core.EmguCV var totalRect = new Rectangle(minX, minY, maxX - minX, maxY - minY); - using Mat contour1Mat = EmguExtensions.InitMat(totalRect.Size); - using Mat contour2Mat = EmguExtensions.InitMat(totalRect.Size); - using Mat overlapCheck = EmguExtensions.InitMat(totalRect.Size); - + using var contour1Mat = EmguExtensions.InitMat(totalRect.Size); + using var contour2Mat = EmguExtensions.InitMat(totalRect.Size); + using var overlapCheck = EmguExtensions.InitMat(totalRect.Size); + var inverseOffset = new Point(minX * -1, minY * -1); CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset); CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset); CvInvoke.BitwiseAnd(contour1Mat, contour2Mat, overlapCheck); - return (CvInvoke.CountNonZero(overlapCheck) > 0); + return CvInvoke.CountNonZero(overlapCheck) > 0; } } diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs index 31a1680..f865b17 100644 --- a/UVtools.Core/Operations/OperationRepairLayers.cs +++ b/UVtools.Core/Operations/OperationRepairLayers.cs @@ -33,12 +33,12 @@ namespace UVtools.Core.Operations private bool _repairIslands = true; private bool _repairResinTraps = true; private bool _removeEmptyLayers = true; - /* Suction Cup repair disabled by default, needs UI component to toggle */ - private bool _repairSuctionCups = false; + private bool _repairSuctionCups; private ushort _removeIslandsBelowEqualPixelCount = 5; private ushort _removeIslandsRecursiveIterations = 4; private ushort _attachIslandsBelowLayers = 2; private byte _resinTrapsOverlapBy = 5; + private byte _suctionCupsVentHole = 16; private uint _gapClosingIterations = 1; private uint _noiseRemovalIterations; @@ -62,7 +62,7 @@ namespace UVtools.Core.Operations { var sb = new StringBuilder(); - if (!RepairIslands && !RemoveEmptyLayers && !RepairResinTraps) + if (!_repairIslands && !_repairResinTraps && !_repairSuctionCups && !_removeEmptyLayers) { sb.AppendLine("You must select at least one repair operation."); } @@ -92,6 +92,12 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _repairResinTraps, value); } + public bool RepairSuctionCups + { + get => _repairSuctionCups; + set => RaiseAndSetIfChanged(ref _repairSuctionCups, value); + } + public bool RemoveEmptyLayers { get => _removeEmptyLayers; @@ -122,6 +128,12 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _resinTrapsOverlapBy, value); } + public byte SuctionCupsVentHole + { + get => _suctionCupsVentHole; + set => RaiseAndSetIfChanged(ref _suctionCupsVentHole, value); + } + public uint GapClosingIterations { get => _gapClosingIterations; @@ -193,9 +205,9 @@ namespace UVtools.Core.Operations Parallel.ForEach(issuesGroup, CoreSettings.ParallelOptions, group => { if (progress.Token.IsCancellationRequested) return; - Layer layer = SlicerFile[group.Key]; - Mat image = layer.LayerMat; - Span bytes = image.GetDataSpan(); + var layer = SlicerFile[group.Key]; + var image = layer.LayerMat; + var bytes = image.GetDataByteSpan(); foreach (var issue in group) { foreach (var issuePixel in issue.Pixels) @@ -382,7 +394,7 @@ namespace UVtools.Core.Operations if (_repairIslands && (_gapClosingIterations > 0 || _noiseRemovalIterations > 0)) { initImage(); - using Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), + using var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1)); if (_gapClosingIterations > 0) { @@ -408,13 +420,12 @@ namespace UVtools.Core.Operations } - if (_repairSuctionCups) + if (_repairSuctionCups && Issues is not null) { /* build out parent/child relationships between all detected suction cups */ - ConcurrentBag bottomSuctionIssues = new(); + var bottomSuctionIssues = new ConcurrentBag(); - var layerCount = SlicerFile.LayerCount; Parallel.ForEach(Issues.Where(issue => issue.Type == LayerIssue.IssueType.SuctionCup), issue => { if (issue.LayerIndex == 0) @@ -422,11 +433,12 @@ namespace UVtools.Core.Operations 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))) { + if (EmguContours.ContoursIntersect(new VectorOfVectorOfPoint(issue.Contours), new VectorOfVectorOfPoint(candidate.Contours))) { overlapFound = true; break; } @@ -439,52 +451,42 @@ namespace UVtools.Core.Operations (bool canDrill, Point location) GetDrillLocation(LayerIssue issue, int diameter) { - var centroid = EmguCV.EmguContour.GetCentroid(new VectorOfPoint(issue.Contours[0])); - using Mat circleCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size); - using Mat contourMat = EmguExtensions.InitMat(issue.BoundingRectangle.Size); - using Mat overlapCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size); + using var vecCentroid = new VectorOfPoint(issue.Contours[0]); + var centroid = EmguContour.GetCentroid(vecCentroid); + using var circleCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size); + using var contourMat = EmguExtensions.InitMat(issue.BoundingRectangle.Size); + using var overlapCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size); var inverseOffset = new Point(issue.BoundingRectangle.X * -1, issue.BoundingRectangle.Y * -1); - - CvInvoke.DrawContours(contourMat, new VectorOfVectorOfPoint(issue.Contours), -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset); + using var vec = new VectorOfVectorOfPoint(issue.Contours); + CvInvoke.DrawContours(contourMat, vec, -1, EmguExtensions.WhiteColor, -1, LineType.EightConnected, null, int.MaxValue, inverseOffset); CvInvoke.Circle(circleCheck, new(centroid.X + inverseOffset.X, centroid.Y + inverseOffset.Y), diameter, EmguExtensions.WhiteColor, -1); CvInvoke.BitwiseAnd(circleCheck, contourMat, overlapCheck); - if (CvInvoke.CountNonZero(overlapCheck) > 0) - { - - /* 5px centroid is inside layer! drill baby drill */ - return (true,centroid); - - } else - { - /* centroid is not inside the actual contour, no drill */ - return (false, new Point()); - } - + return CvInvoke.CountNonZero(overlapCheck) > 0 + ? (true,centroid) /* 5px centroid is inside layer! drill baby drill */ + : (false, new Point()); /* centroid is not inside the actual contour, no drill */ } - List drillOps = new List(); - var suctionReliefSize = (int)Math.Max(SlicerFile.PpmmMax * .5, 15); + + var drillOps = new List(); + //var suctionReliefSize = (ushort)Math.Max(SlicerFile.PpmmMax * 0.8, 17); /* for each suction cup issue that is an initial layer */ foreach (var issue in bottomSuctionIssues) { - var drillPoint = GetDrillLocation(issue, suctionReliefSize); - if (drillPoint.canDrill) - { - drillOps.Add(new PixelDrainHole(issue.LayerIndex, drillPoint.location, (byte)suctionReliefSize)); - } + var drillPoint = GetDrillLocation(issue, _suctionCupsVentHole); + if (!drillPoint.canDrill) continue; + drillOps.Add(new PixelDrainHole(issue.LayerIndex, drillPoint.location, _suctionCupsVentHole)); } SlicerFile.LayerManager.DrawModifications(drillOps, progress); - } if (_removeEmptyLayers) { - List removeLayers = new(); - for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) + var removeLayers = new List(); + for (var layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) { if (SlicerFile[layerIndex].NonZeroPixelCount == 0) { diff --git a/UVtools.Core/PixelEditor/PixelDrainHole.cs b/UVtools.Core/PixelEditor/PixelDrainHole.cs index bf707ab..934864d 100644 --- a/UVtools.Core/PixelEditor/PixelDrainHole.cs +++ b/UVtools.Core/PixelEditor/PixelDrainHole.cs @@ -11,10 +11,10 @@ namespace UVtools.Core.PixelEditor { public class PixelDrainHole : PixelOperation { - private byte _diameter = 50; + private ushort _diameter = 50; public override PixelOperationType OperationType => PixelOperationType.DrainHole; - public byte Diameter + public ushort Diameter { get => _diameter; set => RaiseAndSetIfChanged(ref _diameter, value); @@ -22,7 +22,7 @@ namespace UVtools.Core.PixelEditor public PixelDrainHole(){ _pixelBrightness = 0; } - public PixelDrainHole(uint layerIndex, Point location, byte diameter) : base(layerIndex, location) + public PixelDrainHole(uint layerIndex, Point location, ushort diameter) : base(layerIndex, location) { Diameter = diameter; Size = new Size(diameter, diameter); diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml index 446cf4d..b5fc822 100644 --- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml @@ -12,8 +12,7 @@ + Content="Repair islands"/> @@ -21,8 +20,13 @@ IsChecked="{Binding Operation.RepairResinTraps}" ToolTip.Tip="If enabled, repair will fill black pixels found within a resin trap with white pixels. Hollow areas will become solid." - Content="Repair resin traps" - /> + Content="Repair resin traps"/> + + + @@ -30,12 +34,10 @@ IsChecked="{Binding Operation.RemoveEmptyLayers}" ToolTip.Tip="If enabled, repair will remove all layers with no white pixels. The model will be recalculated to ensure the correct Z height is maintained." - Content="Remove empty layers" - /> + Content="Remove empty layers"/> - + + + + + + diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs index 8d0b68d..cae6b2f 100644 --- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs @@ -24,11 +24,13 @@ namespace UVtools.WPF.Controls.Tools { RepairIslands = UserSettings.Instance.LayerRepair.RepairIslands, RepairResinTraps = UserSettings.Instance.LayerRepair.RepairResinTraps, + RepairSuctionCups = UserSettings.Instance.LayerRepair.RepairSuctionCups, RemoveEmptyLayers = UserSettings.Instance.LayerRepair.RemoveEmptyLayers, RemoveIslandsBelowEqualPixelCount = UserSettings.Instance.LayerRepair.RemoveIslandsBelowEqualPixels, RemoveIslandsRecursiveIterations = UserSettings.Instance.LayerRepair.RemoveIslandsRecursiveIterations, AttachIslandsBelowLayers = UserSettings.Instance.LayerRepair.AttachIslandsBelowLayers, ResinTrapsOverlapBy = UserSettings.Instance.LayerRepair.ResinTrapsOverlapBy, + SuctionCupsVentHole = UserSettings.Instance.LayerRepair.SuctionCupsVentHole, GapClosingIterations = UserSettings.Instance.LayerRepair.ClosingIterations, NoiseRemovalIterations = UserSettings.Instance.LayerRepair.OpeningIterations, }; @@ -37,11 +39,13 @@ namespace UVtools.WPF.Controls.Tools { Operation.RepairIslands = UserSettings.Instance.LayerRepair.RepairIslands; Operation.RepairResinTraps = UserSettings.Instance.LayerRepair.RepairResinTraps; + Operation.RepairSuctionCups = UserSettings.Instance.LayerRepair.RepairSuctionCups; Operation.RemoveEmptyLayers = UserSettings.Instance.LayerRepair.RemoveEmptyLayers; Operation.RemoveIslandsBelowEqualPixelCount = UserSettings.Instance.LayerRepair.RemoveIslandsBelowEqualPixels; Operation.RemoveIslandsRecursiveIterations = UserSettings.Instance.LayerRepair.RemoveIslandsRecursiveIterations; Operation.AttachIslandsBelowLayers = UserSettings.Instance.LayerRepair.AttachIslandsBelowLayers; Operation.ResinTrapsOverlapBy = UserSettings.Instance.LayerRepair.ResinTrapsOverlapBy; + Operation.SuctionCupsVentHole = UserSettings.Instance.LayerRepair.SuctionCupsVentHole; Operation.GapClosingIterations = UserSettings.Instance.LayerRepair.ClosingIterations; Operation.NoiseRemovalIterations = UserSettings.Instance.LayerRepair.OpeningIterations; } diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index fc617d4..685f0d4 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -23,7 +23,6 @@ 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 @@ -87,18 +86,18 @@ namespace UVtools.WPF public List GetOverlappingIssues(LayerIssue targetIssue, int indexOffset) { - List retValue = new(); + var retValue = new List(); 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; - } + using var vec1 = new VectorOfVectorOfPoint(targetIssue.Contours); + using var vec2 = new VectorOfVectorOfPoint(candidate.Contours); + if (!EmguContours.ContoursIntersect(vec1, vec2)) continue; + retValue.Add(candidate); + break; } return retValue; @@ -242,8 +241,8 @@ namespace UVtools.WPF { if (issueRemoveList.Contains(issue)) continue; - Stack upDirection = new Stack(); - Stack downDirection = new Stack(); + var upDirection = new Stack(); + var downDirection = new Stack(); upDirection.Push(issue); downDirection.Push(issue); diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index a0f8670..338e216 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -788,7 +788,7 @@ namespace UVtools.WPF private byte _resinTrapRequiredAreaToProcessCheck = 17; private byte _resinTrapRequiredBlackPixelsToDrain = 10; private byte _resinTrapMaximumPixelBrightnessToDrain = 30; - private uint _suctionCupRequiredAreaToConsider = 5000; + private uint _suctionCupRequiredAreaToConsider = 10000; private byte _touchingBoundMinimumPixelBrightness = 127; private byte _touchingBoundMarginLeft = 5; private byte _touchingBoundMarginTop = 5; @@ -1219,11 +1219,13 @@ namespace UVtools.WPF { private bool _repairIslands = true; private bool _repairResinTraps = true; + private bool _repairSuctionCups; private bool _removeEmptyLayers = true; private ushort _removeIslandsBelowEqualPixels = 5; private ushort _removeIslandsRecursiveIterations = 4; private ushort _attachIslandsBelowLayers = 2; private byte _resinTrapsOverlapBy = 0; + private byte _suctionCupsVentHole = 16; private byte _closingIterations = 2; private byte _openingIterations = 0; @@ -1239,6 +1241,12 @@ namespace UVtools.WPF set => RaiseAndSetIfChanged(ref _repairResinTraps, value); } + public bool RepairSuctionCups + { + get => _repairSuctionCups; + set => RaiseAndSetIfChanged(ref _repairSuctionCups, value); + } + public bool RemoveEmptyLayers { get => _removeEmptyLayers; @@ -1269,6 +1277,12 @@ namespace UVtools.WPF set => RaiseAndSetIfChanged(ref _resinTrapsOverlapBy, value); } + public byte SuctionCupsVentHole + { + get => _suctionCupsVentHole; + set => RaiseAndSetIfChanged(ref _suctionCupsVentHole, value); + } + public byte ClosingIterations { get => _closingIterations; diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 3fe5f2d..a632a77 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -1596,11 +1596,13 @@ + Margin="10,0" Content="Attempt to repair islands by default"/> + Margin="10,0" Content="Attempt to repair resin traps by default"/> + + Margin="10,0" Content="Remove empty layers by default"/> + + + + +