mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
86cd97b526
- (Improvement) Remove developer machine path in PDB / error messages - (Fix) HoneyComb infill not going to the right most edge of the image (#649) - (Fix) AnyCubic machine names were not showing in most files - (Fix) PWMX: Unable to set version 1 - (Fix) LGS: Converted files to lgs cause program to crash with `Value was either too large or too small for a Decimal` (#653)
582 lines
20 KiB
C#
582 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;
|
|
|
|
|
|
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());
|
|
CvInvoke.Imshow("Honeycomb", mask);
|
|
CvInvoke.WaitKey();
|
|
}
|
|
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 halfInfillSpacingD = _infillSpacing / 2.0;
|
|
var halfInfillSpacing = (int)Math.Round(halfInfillSpacingD);
|
|
var halfThickenss = _infillThickness / 2;
|
|
int width = (int)Math.Round(4 * (halfInfillSpacing / Math.Sqrt(3)));
|
|
var infillColor = new MCvScalar(_infillBrightness);
|
|
|
|
int cols = (int)Math.Ceiling((float)targetSize.Width / _infillSpacing) + 2;
|
|
int rows = (int)Math.Ceiling((float)targetSize.Height / _infillSpacing);
|
|
|
|
for (int col = 0; col <= cols; col++)
|
|
{
|
|
for (int row = 0; row <= rows; row++)
|
|
{
|
|
// Move over for the column number.
|
|
int x = (int)Math.Round(halfThickenss + col * (width * 0.75f));
|
|
|
|
// Move down the required number of rows.
|
|
int y = halfThickenss + halfInfillSpacing + 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 - halfInfillSpacing),
|
|
new((int) Math.Round(x + width * 0.75f), y - halfInfillSpacing),
|
|
new(x + width, y),
|
|
new((int) Math.Round(x + width * 0.75f), y + halfInfillSpacing),
|
|
new((int) Math.Round(x + width * 0.25f), y + halfInfillSpacing),
|
|
};
|
|
|
|
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 multiplier = 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 * multiplier;
|
|
y += directions[position].Y * multiplier;
|
|
if (x < 0 || y < 0 || x >= patternMask.Width || y >= patternMask.Height) hitLimits[position] = true;
|
|
points.Add(new Point(x, y));
|
|
position++;
|
|
if (position == 2)
|
|
{
|
|
multiplier++;
|
|
}
|
|
else if (position == 4)
|
|
{
|
|
multiplier++;
|
|
position = 0;
|
|
}
|
|
}
|
|
|
|
|
|
CvInvoke.Polylines(patternMask, points.ToArray(), false, new MCvScalar(_infillBrightness), _infillThickness);
|
|
|
|
return patternMask;
|
|
}
|
|
|
|
#endregion
|
|
} |