Files
UVtools/UVtools.Core/Operations/OperationRaftRelief.cs
T
Tiago Conceição ca6f623a9f v3.6.7
- **Layer:**
   - (Add) Property: `LayerMatBoundingRectangle`
   - (Add) Property: `LayerMatModelBoundingRectangle`
   - (Add) Method: `GetLayerMat(roi)`
- **Issues:**
   - **Islands:**
      - (Improvement) Islands detection performance
      - (Improvement) Required area to consider an island is now real area instead of bounding box area
      - (Fix) Logic bug when combining with overhangs
   - **Overhangs:**
      - (Improvement) Overhangs detection performance
      - (Improvement) Overhangs are now split and identified as separately in the layer
      - (Improvement) Overhangs now shows the correct issue area and able to locate the problem region more precisely
      - (Improvement) Compress overhangs into contours instead of using whole pixels, resulting in better render performance and less memory to hold the issue
      - (Fix) Bug in overhang logic causing to detect the problem twice when combined with supports
   - (Improvement) Touching bounds check logic to spare cycles
- **Tool - Raise platform on print finish:**
   - (Add) Preset "Minimum": Sets to the minimum position
   - (Add) Preset "Medium": Sets to half-way between minimum and maximum position
   - (Add) Preset "Maximum": Sets to the maximum position
   - (Add) Wait time: Sets the ensured wait time to stay still on the desired position.
           This is useful if the printer firmware always move to top and you want to stay still on the set position for at least the desired time.
           Note: The print time calculation will take this wait into consideration and display a longer print time.
- (Add) FileFormat: AnyCubic custom machine (.pwc)
- (Downgrade) OpenCV from 4.5.5 to 4.5.4 due a possible crash while detecting islands (Windows)
2022-10-02 00:44:39 +01:00

543 lines
21 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 Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using Emgu.CV.Util;
using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
namespace UVtools.Core.Operations;
[Serializable]
public class OperationRaftRelief : Operation
{
#region Enums
public enum RaftReliefTypes : byte
{
[Description("Relief: Drill raft to relief pressure and remove some mass")]
Relief,
[Description("Linked lines: Remove the raft, keep supports and link them with lines")]
LinkedLines,
[Description("Dimming: Darkens the raft to cure it less")]
Dimming,
[Description("Decimate: Remove the raft and keep supports only on the plate")]
Decimate,
[Description("Tabs: Creates tabs around the raft to easily insert a tool under it and detach the raft from build plate")]
Tabs
}
#endregion
#region Members
private RaftReliefTypes _reliefType = RaftReliefTypes.Relief;
private uint _maskLayerIndex;
private byte _ignoreFirstLayers;
private byte _lowBrightness;
private byte _dilateIterations = 15;// +/- 1.5mm radius
private byte _wallMargin = 40; // +/- 2mm
private byte _holeDiameter = 80; // +/- 4mm
private byte _holeSpacing = 40; // +/- 2mm
private byte _linkedLineThickness = 26;
private byte _linkedMinimumLinks = 4;
private bool _linkedExternalSupports = true;
private byte _highBrightness = byte.MaxValue;
private ushort _tabTriangleBase = 200;
private ushort _tabTriangleHeight = 250;
#endregion
#region Overrides
public override string IconClass => "fa-solid fa-bowling-ball";
public override string Title => "Raft relief";
public override string Description =>
"Relief raft with a strategy to remove mass, reduce FEP suction, spare resin and easier to remove the prints.";
public override string ConfirmationText =>
$"relief the raft";
public override string ProgressTitle =>
$"Relieving raft";
public override string ProgressAction => "Relieved layers";
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
public override string? ValidateInternally()
{
var sb = new StringBuilder();
if (_reliefType == RaftReliefTypes.Tabs)
{
if(_tabTriangleHeight == 0) sb.AppendLine("The tab height can't be 0");
if(_tabTriangleBase == 0) sb.AppendLine("The tab base can't be 0");
if(_highBrightness == 0) sb.AppendLine("The tab brightness can't be 0");
}
else if (_reliefType == RaftReliefTypes.LinkedLines)
{
if(_linkedLineThickness < 4) sb.AppendLine("The link thickness can't be less than 4");
}
return sb.ToString();
}
public override string ToString()
{
var result = $"[{_reliefType}] [Mask layer: {_maskLayerIndex}] [Ignore: {_ignoreFirstLayers}] [B: {_lowBrightness}] [Dilate: {_dilateIterations}] [Wall margin: {_wallMargin}] [Hole diameter: {_holeDiameter}] [Hole spacing: {_holeSpacing}]";
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
#endregion
#region Constructor
public OperationRaftRelief() { }
public OperationRaftRelief(FileFormat slicerFile) : base(slicerFile) { }
#endregion
#region Properties
public RaftReliefTypes ReliefType
{
get => _reliefType;
set
{
if(!RaiseAndSetIfChanged(ref _reliefType, value)) return;
RaisePropertyChanged(nameof(IsRelief));
RaisePropertyChanged(nameof(IsLinkedLines));
RaisePropertyChanged(nameof(IsDimming));
RaisePropertyChanged(nameof(IsDecimate));
RaisePropertyChanged(nameof(IsTabs));
RaisePropertyChanged(nameof(BrightnessPercent));
}
}
public bool IsRelief => _reliefType == RaftReliefTypes.Relief;
public bool IsLinkedLines => _reliefType == RaftReliefTypes.LinkedLines;
public bool IsDimming => _reliefType == RaftReliefTypes.Dimming;
public bool IsDecimate => _reliefType == RaftReliefTypes.Decimate;
public bool IsTabs => _reliefType == RaftReliefTypes.Tabs;
public uint MaskLayerIndex
{
get => _maskLayerIndex;
set => RaiseAndSetIfChanged(ref _maskLayerIndex, value);
}
public byte IgnoreFirstLayers
{
get => _ignoreFirstLayers;
set => RaiseAndSetIfChanged(ref _ignoreFirstLayers, value);
}
public byte LowBrightness
{
get => _lowBrightness;
set
{
if (!RaiseAndSetIfChanged(ref _lowBrightness, value)) return;
RaisePropertyChanged(nameof(BrightnessPercent));
}
}
public byte HighBrightness
{
get => _highBrightness;
set
{
if(!RaiseAndSetIfChanged(ref _highBrightness, Math.Max((byte) 1, value))) return;
RaisePropertyChanged(nameof(BrightnessPercent));
}
}
public decimal BrightnessPercent => Math.Round((_reliefType is RaftReliefTypes.LinkedLines or RaftReliefTypes.Tabs ? _highBrightness : _lowBrightness) * 100 / 255M, 2);
public byte DilateIterations
{
get => _dilateIterations;
set => RaiseAndSetIfChanged(ref _dilateIterations, value);
}
public byte WallMargin
{
get => _wallMargin;
set => RaiseAndSetIfChanged(ref _wallMargin, value);
}
public byte HoleDiameter
{
get => _holeDiameter;
set => RaiseAndSetIfChanged(ref _holeDiameter, value);
}
public byte HoleSpacing
{
get => _holeSpacing;
set => RaiseAndSetIfChanged(ref _holeSpacing, value);
}
public byte LinkedLineThickness
{
get => _linkedLineThickness;
set => RaiseAndSetIfChanged(ref _linkedLineThickness, Math.Max((byte)4, value));
}
public byte LinkedMinimumLinks
{
get => _linkedMinimumLinks;
set => RaiseAndSetIfChanged(ref _linkedMinimumLinks, value);
}
public bool LinkedExternalSupports
{
get => _linkedExternalSupports;
set => RaiseAndSetIfChanged(ref _linkedExternalSupports, value);
}
public ushort TabTriangleBase
{
get => _tabTriangleBase;
set => RaiseAndSetIfChanged(ref _tabTriangleBase, Math.Max((ushort)5, value));
}
public ushort TabTriangleHeight
{
get => _tabTriangleHeight;
set
{
if (!RaiseAndSetIfChanged(ref _tabTriangleHeight, Math.Max((ushort)5, value))) return;
RaisePropertyChanged(nameof(BrightnessPercent));
}
}
#endregion
#region Equality
protected bool Equals(OperationRaftRelief other)
{
return _reliefType == other._reliefType && _maskLayerIndex == other._maskLayerIndex && _ignoreFirstLayers == other._ignoreFirstLayers && _lowBrightness == other._lowBrightness && _dilateIterations == other._dilateIterations && _wallMargin == other._wallMargin && _holeDiameter == other._holeDiameter && _holeSpacing == other._holeSpacing && _linkedLineThickness == other._linkedLineThickness && _linkedMinimumLinks == other._linkedMinimumLinks && _linkedExternalSupports == other._linkedExternalSupports && _highBrightness == other._highBrightness && _tabTriangleBase == other._tabTriangleBase && _tabTriangleHeight == other._tabTriangleHeight;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((OperationRaftRelief) obj);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add((int) _reliefType);
hashCode.Add(_maskLayerIndex);
hashCode.Add(_ignoreFirstLayers);
hashCode.Add(_lowBrightness);
hashCode.Add(_dilateIterations);
hashCode.Add(_wallMargin);
hashCode.Add(_holeDiameter);
hashCode.Add(_holeSpacing);
hashCode.Add(_linkedLineThickness);
hashCode.Add(_linkedMinimumLinks);
hashCode.Add(_linkedExternalSupports);
hashCode.Add(_highBrightness);
hashCode.Add(_tabTriangleBase);
hashCode.Add(_tabTriangleHeight);
return hashCode.ToHashCode();
}
#endregion
#region Methods
protected override bool ExecuteInternally(OperationProgress progress)
{
progress.ItemCount = 0;
const uint minSupportsRequired = 5;
const uint maxLayerCount = 1000;
Mat? supportsMat = null;
var kernel = EmguExtensions.Kernel3x3Rectangle;
uint firstSupportLayerIndex = _maskLayerIndex;
if (firstSupportLayerIndex <= 0)
{
uint layerCount = Math.Min(SlicerFile.LayerCount, maxLayerCount);
progress.Reset("Tracing raft", layerCount, firstSupportLayerIndex);
for (; firstSupportLayerIndex < layerCount; firstSupportLayerIndex++)
{
progress.ThrowIfCancellationRequested();
supportsMat = GetRoiOrDefault(SlicerFile[firstSupportLayerIndex].LayerMat);
//var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.Gradient, 1, 5, 80, 35, 5, 255); // OLD
var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.GradientAlt, 1.5, 25, 300, 0.80, 5, 255);
if (circles.Length >= minSupportsRequired) break;
supportsMat.Dispose();
supportsMat = null;
progress++;
}
}
else
{
supportsMat = GetRoiOrDefault(SlicerFile[firstSupportLayerIndex].LayerMat);
}
if (supportsMat is null || /*firstSupportLayerIndex == 0 ||*/ _ignoreFirstLayers >= firstSupportLayerIndex) return false;
Mat? patternMat = null;
using var supportsMatOriginal = supportsMat.Clone();
if (_dilateIterations > 0)
{
CvInvoke.Dilate(supportsMat, supportsMat, EmguExtensions.Kernel3x3Rectangle,
EmguExtensions.AnchorCenter, _dilateIterations, BorderType.Reflect101, new MCvScalar());
}
var color = new MCvScalar(255 - _lowBrightness);
switch (ReliefType)
{
case RaftReliefTypes.Relief:
patternMat = supportsMat.NewBlank();
int shapeSize = HoleDiameter + HoleSpacing;
using (var shape = EmguExtensions.InitMat(new Size(shapeSize, shapeSize)))
{
int center = HoleDiameter / 2;
//int centerTwo = operation.HoleDiameter + operation.HoleSpacing + operation.HoleDiameter / 2;
int radius = center;
CvInvoke.Circle(shape, new Point(shapeSize / 2, shapeSize / 2), radius, color, -1);
CvInvoke.Circle(shape, new Point(0, 0), radius / 2, color, -1);
CvInvoke.Circle(shape, new Point(0, shapeSize), radius / 2, color, -1);
CvInvoke.Circle(shape, new Point(shapeSize, 0), radius / 2, color, -1);
CvInvoke.Circle(shape, new Point(shapeSize, shapeSize), radius / 2, color, -1);
CvInvoke.Repeat(shape, supportsMat.Height / shape.Height + 1, supportsMat.Width / shape.Width + 1, patternMat);
patternMat = new Mat(patternMat, new Rectangle(0, 0, supportsMat.Width, supportsMat.Height));
}
break;
case RaftReliefTypes.LinkedLines:
{
using var contours = new EmguContours(supportsMatOriginal.FindContours());
using var supportsRedraw = _linkedExternalSupports ? supportsMatOriginal.Clone() : null;
using var supportsBrightnessCorrection = _highBrightness < byte.MaxValue ? supportsMat.Clone() : null;
var centroidDistance = contours.CalculateCentroidDistances(false, true);
var links = Math.Min(_linkedMinimumLinks, contours.Count-1);
var linkColor = new MCvScalar(_highBrightness);
//var listPoints = new List<Point>();
for (int i = 0; i < contours.Count; i++)
{
if(contours[i].Centroid.IsAnyNegative()) continue;
//listPoints.Add(contours[i].Centroid);
// Link all centroids to each other to calculate the external contour
if (_linkedExternalSupports)
{
for (int x = 0; x < contours.Count; x++)
{
if (x == i) continue;
if (contours[x].Centroid.IsAnyNegative()) continue;
CvInvoke.Line(supportsRedraw, contours[i].Centroid, contours[x].Centroid, linkColor, 4);
}
}
for (int link = 0; link < links; link++)
{
CvInvoke.Line(supportsMat, contours[i].Centroid, centroidDistance[i][link].Contour.Centroid, linkColor, _linkedLineThickness);
}
}
//CvInvoke.Polylines(supportsMat, listPoints.ToArray(), false, linkColor, _linkedLineThickness);
// Link external centroids
if (_linkedExternalSupports)
{
using var externalContours = supportsRedraw!.FindContours(RetrType.External);
CvInvoke.DrawContours(supportsMat, externalContours, -1, linkColor, _linkedLineThickness);
}
// Fix original supports brightness
if (_highBrightness < byte.MaxValue)
{
supportsBrightnessCorrection!.CopyTo(supportsMat, supportsBrightnessCorrection);
}
// Close minor holes, round imperfections, stronger joints
CvInvoke.MorphologyEx(supportsMat, supportsMat, MorphOp.Close, EmguExtensions.Kernel3x3Rectangle, EmguExtensions.AnchorCenter, 1, BorderType.Reflect101, default);
break;
}
case RaftReliefTypes.Dimming:
patternMat = EmguExtensions.InitMat(supportsMat.Size, color);
break;
}
progress.Reset(ProgressAction, firstSupportLayerIndex - _ignoreFirstLayers);
Parallel.For(_ignoreFirstLayers, firstSupportLayerIndex, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
using var mat = SlicerFile[layerIndex].LayerMat;
using var original = mat.Clone();
var target = GetRoiOrDefault(mat);
switch (ReliefType)
{
case RaftReliefTypes.Relief:
case RaftReliefTypes.Dimming:
using (Mat mask = new())
{
/*CvInvoke.Subtract(target, supportsMat, mask);
CvInvoke.Erode(mask, mask, kernel, anchor, operation.WallMargin, BorderType.Reflect101, new MCvScalar());
CvInvoke.Subtract(target, patternMat, target, mask);*/
CvInvoke.Erode(target, mask, kernel, EmguExtensions.AnchorCenter, WallMargin, BorderType.Reflect101, default);
CvInvoke.Subtract(mask, supportsMat, mask);
CvInvoke.Subtract(target, patternMat, target, mask);
}
break;
case RaftReliefTypes.LinkedLines:
case RaftReliefTypes.Decimate:
supportsMat.CopyTo(target);
break;
case RaftReliefTypes.Tabs:
{
using var contours = mat.FindContours(RetrType.External);
var span = mat.GetDataByteSpan();
var minX = 10;
var maxX = mat.Size.Width - 10;
var minY = 10;
var maxY = mat.Size.Height - 10;
var triangleBaseRadius = _tabTriangleBase / 2;
var triangleHeightRadius = _tabTriangleHeight / 2;
var directions = new[]
{
new Point(0, -1), // Up
new Point(1, 0), // Right
new Point(0, 1), // Down
new Point(-1, 0), // Left
};
var color = new MCvScalar(_highBrightness);
for (var i = 0; i < contours.Size; i++)
{
using var contour = new EmguContour(contours[i]);
if(contour.Centroid.IsAnyNegative() || contour.Area < 10000) continue;
for (var dir = 0; dir < directions.Length; dir++)
{
var direction = directions[dir];
var foundFirstWhite = false;
var foundPoint = false;
var x = contour.Centroid.X;
var y = contour.Centroid.Y;
while (!foundPoint
&& x >= minX && x <= maxX && y >= minY && y <= maxY
&& contour.Bounds.Contains(x, y))
{
var pixel = span[mat.GetPixelPos(x, y)];
if (pixel > 0)
{
if (!foundFirstWhite)
{
foundFirstWhite = true;
continue;
}
if (CvInvoke.PointPolygonTest(contours[i], new PointF(x, y), false) == 0) // Must be on edge
{
foundPoint = true;
break;
}
}
x += direction.X;
y += direction.Y;
}
if(!foundPoint) continue;
var polygon = new Point[3];
switch (dir)
{
case 0: // Up
polygon[0] = new Point(Math.Max(10, x - triangleBaseRadius), y);
polygon[1] = new Point(Math.Min(mat.Width - 10, x + triangleBaseRadius), y);
polygon[2] = new Point(x, Math.Max(10, y - triangleHeightRadius));
break;
case 1: // Right
polygon[0] = new Point(x, Math.Max(10, y - triangleBaseRadius));
polygon[1] = new Point(x, Math.Min(mat.Width - 10, y + triangleBaseRadius));
polygon[2] = new Point(Math.Min(mat.Width - 10, x + triangleHeightRadius), y);
break;
case 2: // Down
polygon[0] = new Point(Math.Max(10, x - triangleBaseRadius), y);
polygon[1] = new Point(Math.Min(mat.Width - 10, x + triangleBaseRadius), y);
polygon[2] = new Point(x, Math.Min(mat.Height - 10, y + triangleHeightRadius));
break;
case 3: // Left
polygon[0] = new Point(x, Math.Max(10, y - triangleBaseRadius));
polygon[1] = new Point(x, Math.Min(mat.Width - 10, y + triangleBaseRadius));
polygon[2] = new Point(Math.Max(10, x - triangleHeightRadius), y);
break;
}
CvInvoke.Ellipse(mat, new Point(x, y), new Size(triangleBaseRadius, (int)(triangleBaseRadius / 1.5)), 90 * dir, 0, 180, EmguExtensions.WhiteColor, -1);
using var vec = new VectorOfPoint(polygon);
CvInvoke.FillPoly(mat, vec, color);
}
}
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(ReliefType));
}
ApplyMask(original, target);
SlicerFile[layerIndex].LayerMat = mat;
progress.LockAndIncrement();
});
supportsMat.Dispose();
patternMat?.Dispose();
return !progress.Token.IsCancellationRequested;
}
#endregion
}