Files
UVtools/UVtools.Core/Operations/OperationInfill.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

576 lines
20 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.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
namespace UVtools.Core.Operations;
[Serializable]
public sealed class OperationInfill : Operation
{
#region Members
private InfillAlgorithm _infillType = InfillAlgorithm.CubicCrossAlternating;
private ushort _wallThickness = 64;
private ushort _infillThickness = 45;
private ushort _infillSpacing = 300;
private byte _infillBrightness = 255;
private bool _reinforceInfill;
#endregion
#region Overrides
public override string IconClass => "mdi-checkerboard";
public override string Title => "Infill";
public override string Description =>
$"Generate infill patterns in the model.\n\nNOTES:\n1) You must exclude floor and ceil layers from the range.\n2) You must take care of drain holes after the operation.";
public override string ConfirmationText =>
$"infill model with {InfillType} from layers {LayerIndexStart} through {LayerIndexEnd}?";
public override string ProgressTitle =>
$"Infill model with {InfillType} from layers {LayerIndexStart} through {LayerIndexEnd}";
public override string ProgressAction => "Infilled layers";
#endregion
#region Enums
public enum InfillAlgorithm
{
//Rhombus,
[Description("Straight pillars (Weak)")]
Pillars,
[Description("Concentric (Weak)")]
Concentric,
[Description("Waves (Medium)")]
Waves,
[Description("Cubic cross: Fixed pilars with crossing sections (Optimal)")]
CubicCross,
[Description("Cubic alternating cross: Fixed pilars with crossing sections alternating in directions (Optimal)")]
CubicCrossAlternating,
[Description("Cubic star: Fixed pilars with crossing sections in star pattern (Strong)")]
CubicStar,
[Description("Honeycomb (Strong)")]
Honeycomb,
[Description("Gyroid (Strong)")]
Gyroid,
}
#endregion
#region Properties
public InfillAlgorithm InfillType
{
get => _infillType;
set => RaiseAndSetIfChanged(ref _infillType, value);
}
public ushort WallThickness
{
get => _wallThickness;
set => RaiseAndSetIfChanged(ref _wallThickness, value);
}
public byte InfillBrightness
{
get => _infillBrightness;
set => RaiseAndSetIfChanged(ref _infillBrightness, value);
}
public ushort InfillThickness
{
get => _infillThickness;
set => RaiseAndSetIfChanged(ref _infillThickness, value);
}
public ushort InfillSpacing
{
get => _infillSpacing;
set => RaiseAndSetIfChanged(ref _infillSpacing, value);
}
public bool ReinforceInfill
{
get => _reinforceInfill;
set => RaiseAndSetIfChanged(ref _reinforceInfill, value);
}
public override string ToString()
{
var result = $"[{_infillType}] [Wall: {_wallThickness}px] [B: {_infillBrightness}px] [T: {_infillThickness}px] [S: {_infillSpacing}px] [R: {_reinforceInfill}]" + LayerRangeString;
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
#endregion
#region Constructor
public OperationInfill() { }
public OperationInfill(FileFormat slicerFile) : base(slicerFile) { }
#endregion
#region Equality
private bool Equals(OperationInfill other)
{
return _infillType == other._infillType && _wallThickness == other._wallThickness && _infillThickness == other._infillThickness && _infillSpacing == other._infillSpacing && _infillBrightness == other._infillBrightness && _reinforceInfill == other._reinforceInfill;
}
public override bool Equals(object? obj)
{
return ReferenceEquals(this, obj) || obj is OperationInfill other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine((int) _infillType, _wallThickness, _infillThickness, _infillSpacing, _infillBrightness, _reinforceInfill);
}
#endregion
#region Methods
protected override bool ExecuteInternally(OperationProgress progress)
{
Mat? mask = null;
if (_infillType == InfillAlgorithm.Honeycomb)
{
mask = GetHoneycombMask(GetRoiSizeOrVolumeSize());
}
else if (_infillType == InfillAlgorithm.Concentric)
{
mask = GetConcentricMask(GetRoiSizeOrVolumeSize());
}
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat, layerIndex, mask!);
SlicerFile[layerIndex].LayerMat = mat;
progress.LockAndIncrement();
});
mask?.Dispose();
return !progress.Token.IsCancellationRequested;
}
public override bool Execute(Mat mat, params object[]? arguments)
{
if (arguments is null || arguments.Length < 1) return false;
var kernel = EmguExtensions.Kernel3x3Rectangle;
uint index = Convert.ToUInt32(arguments[0]);
uint layerIndex = index - LayerIndexStart;
var infillColor = new MCvScalar(_infillBrightness);
Mat? patternMask = null;
using Mat erode = new ();
using Mat diff = new ();
var target = GetRoiOrVolumeBounds(mat);
using var mask = GetMask(mat);
bool disposeTargetMask = true;
if (_infillType is InfillAlgorithm.Pillars
or InfillAlgorithm.CubicCross
or InfillAlgorithm.CubicCrossAlternating
or InfillAlgorithm.CubicStar)
{
using var infillPattern = EmguExtensions.InitMat(new Size(_infillSpacing, _infillSpacing));
using var matPattern = mat.NewBlank();
bool firstPattern = true;
uint accumulator = 0;
bool dynamicCenter = false;
while (accumulator < layerIndex)
{
dynamicCenter = !dynamicCenter;
firstPattern = true;
accumulator += _infillSpacing;
if (accumulator >= layerIndex) break;
if (_reinforceInfill)
{
firstPattern = false;
accumulator += _infillThickness;
}
}
if (firstPattern)
{
int thickness = _infillThickness / 2;
// Top Left
CvInvoke.Rectangle(infillPattern,
new Rectangle(0, 0, thickness, thickness),
infillColor, -1);
// Top Right
CvInvoke.Rectangle(infillPattern,
new Rectangle(infillPattern.Width - thickness, 0, thickness, thickness),
infillColor, -1);
// Bottom Left
CvInvoke.Rectangle(infillPattern,
new Rectangle(0, infillPattern.Height - thickness, thickness, thickness),
infillColor, -1);
// Bottom Right
CvInvoke.Rectangle(infillPattern,
new Rectangle(infillPattern.Width - thickness, infillPattern.Height - thickness,
thickness, thickness),
infillColor, -1);
// Center cross
int margin = (int) (InfillSpacing - accumulator + layerIndex) - thickness;
int marginInv = (int) (accumulator - layerIndex) - thickness;
if (_infillType == InfillAlgorithm.CubicCross ||
(_infillType == InfillAlgorithm.CubicCrossAlternating &&
dynamicCenter) ||
_infillType == InfillAlgorithm.CubicStar)
{
CvInvoke.Rectangle(infillPattern,
new Rectangle(margin, margin, _infillThickness, _infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(marginInv, marginInv, _infillThickness,
_infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(margin, marginInv, _infillThickness,
_infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(marginInv, margin, _infillThickness,
_infillThickness),
infillColor, -1);
}
if (_infillType == InfillAlgorithm.CubicStar ||
(_infillType == InfillAlgorithm.CubicCrossAlternating &&
!dynamicCenter))
{
CvInvoke.Rectangle(infillPattern,
new Rectangle(margin, -thickness, _infillThickness,
_infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(marginInv, -thickness, _infillThickness,
_infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(-thickness, margin, _infillThickness,
_infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(-thickness, marginInv, _infillThickness,
_infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(InfillSpacing - thickness, margin,
_infillThickness, _infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(InfillSpacing - thickness, marginInv,
_infillThickness, _infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(margin, InfillSpacing - thickness,
_infillThickness, _infillThickness),
infillColor, -1);
CvInvoke.Rectangle(infillPattern,
new Rectangle(marginInv, InfillSpacing - thickness,
_infillThickness, _infillThickness),
infillColor, -1);
}
}
else
{
CvInvoke.Rectangle(infillPattern,
new Rectangle(0, 0, _infillSpacing, _infillSpacing),
infillColor, _infillThickness);
}
CvInvoke.Repeat(infillPattern, target.Rows / infillPattern.Rows + 1,
target.Cols / infillPattern.Cols + 1, matPattern);
patternMask = matPattern.Roi(target);
disposeTargetMask = true;
}
else if (_infillType == InfillAlgorithm.Honeycomb)
{
if (arguments.Length >= 2)
{
patternMask = (Mat)arguments[1];
disposeTargetMask = false;
}
else
{
patternMask = GetHoneycombMask(target.Size);
disposeTargetMask = true;
}
}
else if (_infillType == InfillAlgorithm.Concentric)
{
if (arguments.Length >= 2)
{
patternMask = (Mat)arguments[1];
disposeTargetMask = false;
}
else
{
patternMask = GetConcentricMask(target.Size);
disposeTargetMask = true;
}
}
else if (_infillType == InfillAlgorithm.Waves)
{
var sineHeight = 100;
var sineWidth = 100;
var radius = (ushort)(_infillThickness / 2);
var points = new List<Point>();
bool isHorizontal = true;
float accumulator = 0;
for (int i = 0; i <= layerIndex; i++)
{
accumulator += SlicerFile[index].RelativePositionZ;
if (accumulator >= 2)
{
isHorizontal = !isHorizontal;
accumulator = 0;
}
}
//using var infillPattern = EmguExtensions.InitMat(new Size(_infillSpacing, _infillSpacing));
//using var matPattern = new Mat();
using var infillPattern = mat.NewBlank();
int maxY = 0;
if (isHorizontal)
{
for (int x = 0; x < mat.Width; x += radius)
{
int y = (int) (Math.Sin((double) x / sineWidth /*+ sineWidth * layerIndex*/) * sineHeight / 2.0 +
sineHeight / 2.0 + radius);
points.Add(new Point(x, y));
maxY = Math.Max(maxY, y);
}
var infillPatternRoi = infillPattern.Roi(new Size(infillPattern.Width, maxY + radius + 2 + _infillSpacing));
CvInvoke.Polylines(infillPatternRoi, points.ToArray(), false, infillColor, _infillThickness);
CvInvoke.Repeat(infillPatternRoi, target.Rows / infillPatternRoi.Rows + 1, 1, infillPattern);
}
else
{
for (int y = 0; y < mat.Height; y += radius)
{
int x = (int)(Math.Sin((double)y / sineWidth /*+ sineWidth * layerIndex*/) * sineHeight / 2.0 +
sineHeight / 2.0 + radius);
points.Add(new Point(x, y));
maxY = Math.Max(maxY, x);
}
var infillPatternRoi = infillPattern.Roi(new Size(maxY + radius + 2 + _infillSpacing, infillPattern.Height));
CvInvoke.Polylines(infillPatternRoi, points.ToArray(), false, infillColor, _infillThickness);
CvInvoke.Repeat(infillPatternRoi, 1, target.Cols / infillPatternRoi.Cols + 1, infillPattern);
}
points.Clear();
patternMask = infillPattern.Roi(target);
disposeTargetMask = true;
}
else if (_infillType == InfillAlgorithm.Gyroid)
{
patternMask = target.NewBlank();
var scaleRatio = 0.0012 / (_infillSpacing + _infillThickness / 2);
//var scaleX = 0.04 * _infillSpacing * Math.PI / target.Width;
//var scaleY = 0.04 * _infillSpacing * Math.PI / target.Height;
var scaleX = scaleRatio * mat.Width;
var scaleY = scaleRatio * mat.Height;
//const double scaleZ = 2.0 * Math.PI;
//var dz = 2.0 * scaleZ / LayerRangeCount; // z step
//var zz = 0.05 * (SlicerFile[index].LayerHeight / 0.05f) * (layerIndex + 1);
float zz = 0;
for (var i = LayerIndexStart; i <= index; i++)
{
zz += SlicerFile[i].RelativePositionZ;
}
for (int y = 0; y < patternMask.Height; y++)
{
var span = patternMask.GetRowSpan<byte>(y);
var yy = y * scaleY; // y position of pixel
for (int x = 0; x < patternMask.Width; x++)
{
var xx = x * scaleX; // x position of pixel
var d = Math.Sin(xx) * Math.Cos(yy) // compute gyroid equation
+ Math.Sin(yy) * Math.Cos(zz)
+ Math.Sin(zz) * Math.Cos(xx);
//if (d > 1e-6) continue; // Far from surface
if (Math.Abs(d) - 0.006*_infillThickness > 0) continue;
//if (d - 0.05 > 1e-6) continue;
span[x] = _infillBrightness;
}
}
//using var contours = patternMask.FindContours();
//CvInvoke.DrawContours(patternMask, contours, -1, infillColor, _infillThickness);
disposeTargetMask = true;
}
//patternMask.Save("D:\\pattern.png");
CvInvoke.Erode(target, erode, kernel, EmguExtensions.AnchorCenter, WallThickness, BorderType.Reflect101,
default);
CvInvoke.Subtract(target, erode, diff);
CvInvoke.BitwiseAnd(erode, patternMask, target, mask);
CvInvoke.Add(target, diff, target, mask);
if (disposeTargetMask)
{
patternMask!.Dispose();
}
return true;
}
public Mat GetHoneycombMask(Size targetSize)
{
var patternMask = EmguExtensions.InitMat(targetSize);
var halfInfillSpacing = _infillSpacing / 2;
var halfThickenss = _infillThickness / 2;
int width = (int)Math.Round(4 * (_infillSpacing / 2.0 / Math.Sqrt(3)));
var infillColor = new MCvScalar(_infillBrightness);
for (int col = 0; col <= targetSize.Width / _infillSpacing; col++)
{
for (int row = 0; row <= targetSize.Height / _infillSpacing; row++)
{
// Move over for the column number.
int x = (int)Math.Round(col * (width * 0.75f));
// Move down the required number of rows.
int y = row * _infillSpacing;
// If the column is odd, move down half a hex more.
if (col % 2 == 1) y += halfInfillSpacing;
var points = new Point[]
{
new(x, y),
new((int) Math.Round(x + width * 0.25f), y - _infillSpacing / 2),
new((int) Math.Round(x + width * 0.75f), y - _infillSpacing / 2),
new(x + width, y),
new((int) Math.Round(x + width * 0.75f), y + _infillSpacing / 2),
new((int) Math.Round(x + width * 0.25f), y + _infillSpacing / 2),
};
CvInvoke.Polylines(patternMask, points, true, infillColor, _infillThickness);
}
}
return patternMask;
}
public Mat GetConcentricMask(Size targetSize)
{
var patternMask = EmguExtensions.InitMat(targetSize);
//var halfInfillSpacing = _infillSpacing / 2;
//var halfThickenss = _infillThickness / 2;
int multiplicator = 1;
byte position = 0;
int x = patternMask.Width / 2;
int y = patternMask.Height / 2;
Point[] directions = {
new(0, -_infillSpacing), // top
new(_infillSpacing, 0), // right
new(0, _infillSpacing), // bottom
new(-_infillSpacing, 0), // left
};
bool[] hitLimits =
{
false,
false,
false,
false
};
var points = new List<Point> {new(x, y)};
while (hitLimits.Any(hitLimit => !hitLimit))
{
x += directions[position].X * multiplicator;
y += directions[position].Y * multiplicator;
if (x < 0 || y < 0 || x >= patternMask.Width || y >= patternMask.Height) hitLimits[position] = true;
points.Add(new Point(x, y));
position++;
if (position == 2)
{
multiplicator++;
}
else if (position == 4)
{
multiplicator++;
position = 0;
}
}
CvInvoke.Polylines(patternMask, points.ToArray(), false, new MCvScalar(_infillBrightness), _infillThickness);
return patternMask;
}
#endregion
}