mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-13 20:07:40 +02:00
ca6f623a9f
- **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)
759 lines
28 KiB
C#
759 lines
28 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.Drawing;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
using UVtools.Core.Layers;
|
|
|
|
namespace UVtools.Core.Operations;
|
|
|
|
[Serializable]
|
|
public sealed class OperationCalibrateTolerance : Operation
|
|
{
|
|
#region Members
|
|
private decimal _displayWidth;
|
|
private decimal _displayHeight;
|
|
private decimal _layerHeight;
|
|
private ushort _bottomLayers;
|
|
private decimal _bottomExposure;
|
|
private decimal _normalExposure;
|
|
private decimal _zSize = 10;
|
|
private ushort _topBottomMargin = 100;
|
|
private ushort _leftRightMargin = 100;
|
|
private byte _chamferLayers = 4;
|
|
private byte _erodeBottomIterations = 4;
|
|
private Shapes _shape = Shapes.Circle;
|
|
private ushort _partMargin = 50;
|
|
private bool _outputSameDiameterPart = true;
|
|
private bool _fuseParts;
|
|
private bool _enableAntiAliasing = true;
|
|
private bool _mirrorOutput;
|
|
|
|
private decimal _femaleDiameter = 16;
|
|
private decimal _femaleHoleDiameter = 10;
|
|
|
|
private ushort _maleThinnerModels = 5;
|
|
private decimal _maleThinnerOffset;
|
|
private decimal _maleThinnerStep = -0.1M;
|
|
private ushort _maleThickerModels;
|
|
private decimal _maleThickerOffset;
|
|
private decimal _maleThickerStep = 0.1M;
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
public override bool CanROI => false;
|
|
|
|
public override bool CanCancel => false;
|
|
|
|
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
|
|
public override string IconClass => "fa-solid fa-dot-circle";
|
|
public override string Title => "Tolerance";
|
|
public override string Description =>
|
|
"Generates test models with various strategies and increments to verify the part tolerances.\n" +
|
|
"You must repeat this test when change any of the following: printer, LEDs, resin and exposure times.\n" +
|
|
"Note: The current opened file will be overwritten with this test, use a dummy or a not needed file.";
|
|
|
|
public override string ConfirmationText =>
|
|
$"generate the tolerance test?";
|
|
|
|
public override string ProgressTitle =>
|
|
$"Generating the tolerance test";
|
|
|
|
public override string ProgressAction => "Generated";
|
|
|
|
public override string? ValidateInternally()
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (_displayWidth <= 0)
|
|
{
|
|
sb.AppendLine("Display width must be a positive value.");
|
|
}
|
|
|
|
if (_displayHeight <= 0)
|
|
{
|
|
sb.AppendLine("Display height must be a positive value.");
|
|
}
|
|
|
|
if (_femaleHoleDiameter >= _femaleDiameter)
|
|
{
|
|
sb.AppendLine("Hole diameter must be smaller than female diameter.");
|
|
}
|
|
|
|
if (OutputObjects <= 0)
|
|
{
|
|
sb.AppendLine("No objects to output.");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var result = $"[Layer Height: {_layerHeight}] " +
|
|
$"[Bottom layers: {_bottomLayers}] " +
|
|
$"[Exposure: {_bottomExposure}/{_normalExposure}] " +
|
|
$"[Z: {_zSize}] " +
|
|
$"[TB:{_topBottomMargin} LR:{_leftRightMargin} PM:{_partMargin}] " +
|
|
$"[Chamfer: {_chamferLayers}] [Erode: {_erodeBottomIterations}] " +
|
|
$"[OSHD: {_outputSameDiameterPart}] [Fuse: {_fuseParts}] [AA: {_enableAntiAliasing}] [Mirror: {_mirrorOutput}]" +
|
|
$"[{_shape}, {_femaleDiameter}/{_femaleHoleDiameter}] " +
|
|
$"[tM: {_maleThinnerModels} O:{_maleThinnerOffset} S:{_maleThinnerStep}] " +
|
|
$"[TM: {_maleThickerModels} O:{_maleThickerOffset} S:{_maleThickerStep}] ";
|
|
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public decimal DisplayWidth
|
|
{
|
|
get => _displayWidth;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _displayWidth, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(Xppmm));
|
|
}
|
|
}
|
|
|
|
public decimal DisplayHeight
|
|
{
|
|
get => _displayHeight;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _displayHeight, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(Yppmm));
|
|
}
|
|
}
|
|
|
|
public decimal Xppmm => DisplayWidth > 0 ? Math.Round(SlicerFile.Resolution.Width / DisplayWidth, 2) : 0;
|
|
public decimal Yppmm => DisplayHeight > 0 ? Math.Round(SlicerFile.Resolution.Height / DisplayHeight, 2) : 0;
|
|
|
|
public decimal LayerHeight
|
|
{
|
|
get => _layerHeight;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
|
|
RaisePropertyChanged(nameof(BottomLayersMM));
|
|
RaisePropertyChanged(nameof(LayerCount));
|
|
RaisePropertyChanged(nameof(RealZSize));
|
|
//RaisePropertyChanged(nameof(ObservedZSize));
|
|
}
|
|
}
|
|
|
|
public ushort Microns => (ushort)(LayerHeight * 1000);
|
|
|
|
public ushort BottomLayers
|
|
{
|
|
get => _bottomLayers;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _bottomLayers, value)) return;
|
|
RaisePropertyChanged(nameof(BottomLayersMM));
|
|
}
|
|
}
|
|
|
|
public decimal BottomLayersMM => Layer.RoundHeight(LayerHeight * BottomLayers);
|
|
|
|
public decimal BottomExposure
|
|
{
|
|
get => _bottomExposure;
|
|
set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal NormalExposure
|
|
{
|
|
get => _normalExposure;
|
|
set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal ZSize
|
|
{
|
|
get => _zSize;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _zSize, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(LayerCount));
|
|
RaisePropertyChanged(nameof(RealZSize));
|
|
//RaisePropertyChanged(nameof(ObservedZSize));
|
|
}
|
|
}
|
|
|
|
public uint LayerCount => (uint)(ZSize / LayerHeight);
|
|
|
|
public decimal RealZSize => LayerCount * _layerHeight;
|
|
|
|
public ushort TopBottomMargin
|
|
{
|
|
get => _topBottomMargin;
|
|
set => RaiseAndSetIfChanged(ref _topBottomMargin, value);
|
|
}
|
|
|
|
public ushort LeftRightMargin
|
|
{
|
|
get => _leftRightMargin;
|
|
set => RaiseAndSetIfChanged(ref _leftRightMargin, value);
|
|
}
|
|
|
|
public byte ChamferLayers
|
|
{
|
|
get => _chamferLayers;
|
|
set => RaiseAndSetIfChanged(ref _chamferLayers, value);
|
|
}
|
|
|
|
public byte ErodeBottomIterations
|
|
{
|
|
get => _erodeBottomIterations;
|
|
set => RaiseAndSetIfChanged(ref _erodeBottomIterations, value);
|
|
}
|
|
|
|
public Shapes Shape
|
|
{
|
|
get => _shape;
|
|
set => RaiseAndSetIfChanged(ref _shape, value);
|
|
}
|
|
|
|
public ushort PartMargin
|
|
{
|
|
get => _partMargin;
|
|
set => RaiseAndSetIfChanged(ref _partMargin, value);
|
|
}
|
|
|
|
public bool OutputSameDiameterPart
|
|
{
|
|
get => _outputSameDiameterPart;
|
|
set => RaiseAndSetIfChanged(ref _outputSameDiameterPart, value);
|
|
}
|
|
|
|
public bool FuseParts
|
|
{
|
|
get => _fuseParts;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _fuseParts, value)) return;
|
|
if (value)
|
|
{
|
|
OutputSameDiameterPart = false;
|
|
MaleThickerModels = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool EnableAntiAliasing
|
|
{
|
|
get => _enableAntiAliasing;
|
|
set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value);
|
|
}
|
|
|
|
public bool MirrorOutput
|
|
{
|
|
get => _mirrorOutput;
|
|
set => RaiseAndSetIfChanged(ref _mirrorOutput, value);
|
|
}
|
|
|
|
public decimal FemaleDiameter
|
|
{
|
|
get => _femaleDiameter;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _femaleDiameter, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(FemaleDiameterXPixels));
|
|
RaisePropertyChanged(nameof(FemaleDiameterYPixels));
|
|
RaisePropertyChanged(nameof(FemaleDiameterRealXSize));
|
|
RaisePropertyChanged(nameof(FemaleDiameterRealYSize));
|
|
}
|
|
}
|
|
|
|
public uint FemaleDiameterXPixels => (uint)(_femaleDiameter * Xppmm);
|
|
public uint FemaleDiameterYPixels => (uint)(_femaleDiameter * Yppmm);
|
|
|
|
public decimal FemaleDiameterRealXSize
|
|
{
|
|
get
|
|
{
|
|
decimal pixels = _femaleDiameter * Xppmm;
|
|
return pixels <= 0 ? 0 : Math.Round(_femaleDiameter - (pixels - Math.Truncate(pixels)) / Xppmm, 2);
|
|
}
|
|
}
|
|
|
|
public decimal FemaleDiameterRealYSize
|
|
{
|
|
get
|
|
{
|
|
decimal pixels = _femaleDiameter * Yppmm;
|
|
return pixels <= 0 ? 0 : Math.Round(_femaleDiameter - (pixels - Math.Truncate(pixels)) / Yppmm, 2);
|
|
}
|
|
}
|
|
|
|
public decimal FemaleHoleDiameter
|
|
{
|
|
get => _femaleHoleDiameter;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _femaleHoleDiameter, value)) return;
|
|
RaisePropertyChanged(nameof(FemaleHoleDiameterXPixels));
|
|
RaisePropertyChanged(nameof(FemaleHoleDiameterYPixels));
|
|
RaisePropertyChanged(nameof(FemaleHoleDiameterRealXSize));
|
|
RaisePropertyChanged(nameof(FemaleHoleDiameterRealYSize));
|
|
}
|
|
}
|
|
|
|
public uint FemaleHoleDiameterXPixels => (uint)(_femaleHoleDiameter * Xppmm);
|
|
public uint FemaleHoleDiameterYPixels => (uint)(_femaleHoleDiameter * Yppmm);
|
|
|
|
public decimal FemaleHoleDiameterRealXSize
|
|
{
|
|
get
|
|
{
|
|
decimal pixels = _femaleHoleDiameter * Xppmm;
|
|
return pixels <= 0 ? 0 : Math.Round(_femaleHoleDiameter - (pixels - Math.Truncate(pixels)) / Xppmm, 2);
|
|
}
|
|
}
|
|
|
|
public decimal FemaleHoleDiameterRealYSize
|
|
{
|
|
get
|
|
{
|
|
decimal pixels = _femaleHoleDiameter * Yppmm;
|
|
return pixels <= 0 ? 0 : Math.Round(_femaleHoleDiameter - (pixels - Math.Truncate(pixels)) / Yppmm, 2);
|
|
}
|
|
}
|
|
|
|
public ushort MaleThinnerModels
|
|
{
|
|
get => _maleThinnerModels;
|
|
set => RaiseAndSetIfChanged(ref _maleThinnerModels, value);
|
|
}
|
|
|
|
public decimal MaleThinnerOffset
|
|
{
|
|
get => _maleThinnerOffset;
|
|
set => RaiseAndSetIfChanged(ref _maleThinnerOffset, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal MaleThinnerStep
|
|
{
|
|
get => _maleThinnerStep;
|
|
set => RaiseAndSetIfChanged(ref _maleThinnerStep, Math.Round(value, 2));
|
|
}
|
|
|
|
public ushort MaleThickerModels
|
|
{
|
|
get => _maleThickerModels;
|
|
set => RaiseAndSetIfChanged(ref _maleThickerModels, value);
|
|
}
|
|
|
|
public decimal MaleThickerOffset
|
|
{
|
|
get => _maleThickerOffset;
|
|
set => RaiseAndSetIfChanged(ref _maleThickerOffset, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal MaleThickerStep
|
|
{
|
|
get => _maleThickerStep;
|
|
set => RaiseAndSetIfChanged(ref _maleThickerStep, Math.Round(value, 2));
|
|
}
|
|
|
|
|
|
public uint OutputObjects =>
|
|
(_outputSameDiameterPart ? 1u : 0) +
|
|
_maleThinnerModels +
|
|
_maleThickerModels +
|
|
(_fuseParts ? 0 : 1u);
|
|
|
|
/*public decimal ObservedXSize
|
|
{
|
|
get => _observedXSize;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _observedXSize, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(ScaleXFactor));
|
|
}
|
|
}
|
|
|
|
public decimal ObservedYSize
|
|
{
|
|
get => _observedYSize;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _observedYSize, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(ScaleYFactor));
|
|
}
|
|
}
|
|
|
|
public decimal ObservedZSize
|
|
{
|
|
get => _observedZSize;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _observedZSize, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(ScaleZFactor));
|
|
}
|
|
}
|
|
|
|
|
|
public decimal ScaleXFactor => ObservedXSize > 0 && RealXSize > 0 ? Math.Round(RealXSize * 100 / ObservedXSize, 2) : 100;
|
|
public decimal ScaleYFactor => ObservedYSize > 0 && RealYSize > 0 ? Math.Round(RealYSize * 100 / ObservedYSize, 2) : 100;
|
|
public decimal ScaleZFactor => ObservedZSize > 0 && RealZSize > 0 ? Math.Round(RealZSize * 100 / ObservedZSize, 2) : 100;
|
|
*/
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public OperationCalibrateTolerance() { }
|
|
|
|
public OperationCalibrateTolerance(FileFormat slicerFile) : base(slicerFile)
|
|
{ }
|
|
|
|
public override void InitWithSlicerFile()
|
|
{
|
|
base.InitWithSlicerFile();
|
|
if (_layerHeight <= 0) _layerHeight = (decimal)SlicerFile.LayerHeight;
|
|
if (_bottomLayers <= 0) _bottomLayers = SlicerFile.BottomLayerCount;
|
|
if (_bottomExposure <= 0) _bottomExposure = (decimal)SlicerFile.BottomExposureTime;
|
|
if (_normalExposure <= 0) _normalExposure = (decimal)SlicerFile.ExposureTime;
|
|
_mirrorOutput = SlicerFile.DisplayMirror != FlipDirection.None;
|
|
|
|
if (SlicerFile.DisplayWidth > 0)
|
|
DisplayWidth = (decimal)SlicerFile.DisplayWidth;
|
|
if (SlicerFile.DisplayHeight > 0)
|
|
DisplayHeight = (decimal)SlicerFile.DisplayHeight;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Enums
|
|
|
|
public enum Shapes : byte
|
|
{
|
|
Circle,
|
|
Square
|
|
}
|
|
|
|
public static Array ShapesItems => Enum.GetValues(typeof(Shapes));
|
|
#endregion
|
|
|
|
#region Equality
|
|
|
|
private bool Equals(OperationCalibrateTolerance other)
|
|
{
|
|
return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _zSize == other._zSize && _topBottomMargin == other._topBottomMargin && _leftRightMargin == other._leftRightMargin && _chamferLayers == other._chamferLayers && _erodeBottomIterations == other._erodeBottomIterations && _shape == other._shape && _partMargin == other._partMargin && _outputSameDiameterPart == other._outputSameDiameterPart && _fuseParts == other._fuseParts && _enableAntiAliasing == other._enableAntiAliasing && _femaleDiameter == other._femaleDiameter && _femaleHoleDiameter == other._femaleHoleDiameter && _maleThinnerModels == other._maleThinnerModels && _maleThinnerOffset == other._maleThinnerOffset && _maleThinnerStep == other._maleThinnerStep && _maleThickerModels == other._maleThickerModels && _maleThickerOffset == other._maleThickerOffset && _maleThickerStep == other._maleThickerStep && _mirrorOutput == other._mirrorOutput;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return ReferenceEquals(this, obj) || obj is OperationCalibrateTolerance other && Equals(other);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hashCode = new HashCode();
|
|
hashCode.Add(_layerHeight);
|
|
hashCode.Add(_bottomLayers);
|
|
hashCode.Add(_bottomExposure);
|
|
hashCode.Add(_normalExposure);
|
|
hashCode.Add(_zSize);
|
|
hashCode.Add(_topBottomMargin);
|
|
hashCode.Add(_leftRightMargin);
|
|
hashCode.Add(_chamferLayers);
|
|
hashCode.Add(_erodeBottomIterations);
|
|
hashCode.Add((int) _shape);
|
|
hashCode.Add(_partMargin);
|
|
hashCode.Add(_outputSameDiameterPart);
|
|
hashCode.Add(_fuseParts);
|
|
hashCode.Add(_enableAntiAliasing);
|
|
hashCode.Add(_mirrorOutput);
|
|
hashCode.Add(_femaleDiameter);
|
|
hashCode.Add(_femaleHoleDiameter);
|
|
hashCode.Add(_maleThinnerModels);
|
|
hashCode.Add(_maleThinnerOffset);
|
|
hashCode.Add(_maleThinnerStep);
|
|
hashCode.Add(_maleThickerModels);
|
|
hashCode.Add(_maleThickerOffset);
|
|
hashCode.Add(_maleThickerStep);
|
|
return hashCode.ToHashCode();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
public Mat[] GetLayers()
|
|
{
|
|
var layers = new Mat[LayerCount];
|
|
var layer = EmguExtensions.InitMat(SlicerFile.Resolution);
|
|
|
|
ushort startX = Math.Max((ushort)2, _leftRightMargin);
|
|
ushort startY = Math.Max((ushort)2, _topBottomMargin);
|
|
int currentX = startX;
|
|
int currentY = startY;
|
|
|
|
const FontFace fontFace = FontFace.HersheyDuplex;
|
|
const double fontScale = 1;
|
|
const byte fontThickness = 2;
|
|
LineType lineType = _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected;
|
|
|
|
var kernel = EmguExtensions.Kernel3x3Rectangle;
|
|
|
|
var pointTextList = new List<KeyValuePair<Point, string>>();
|
|
|
|
if (!_fuseParts)
|
|
{
|
|
switch (Shape)
|
|
{
|
|
case Shapes.Circle:
|
|
currentX += (int) FemaleDiameterXPixels / 2;
|
|
currentY += (int) FemaleDiameterXPixels / 2;
|
|
CvInvoke.Circle(layer, new Point(currentX, currentY), (int) (FemaleDiameterXPixels / 2), EmguExtensions.WhiteColor, -1, lineType);
|
|
CvInvoke.Circle(layer, new Point(currentX, currentY), (int) (FemaleHoleDiameterXPixels / 2), EmguExtensions.BlackColor, -1, lineType);
|
|
currentX += (int) FemaleDiameterXPixels / 2 + PartMargin;
|
|
|
|
break;
|
|
case Shapes.Square:
|
|
int offsetX = (int) ((FemaleDiameterXPixels - FemaleHoleDiameterXPixels) / 2);
|
|
int offsetY = (int) ((FemaleDiameterYPixels - FemaleHoleDiameterYPixels) / 2);
|
|
CvInvoke.Rectangle(layer, new Rectangle(currentX, currentY, (int) FemaleDiameterXPixels, (int) FemaleDiameterXPixels), EmguExtensions.WhiteColor, -1, lineType);
|
|
CvInvoke.Rectangle(layer, new Rectangle(currentX + offsetX, currentY + offsetY, (int) FemaleHoleDiameterXPixels, (int) FemaleHoleDiameterYPixels), EmguExtensions.BlackColor, -1, lineType);
|
|
currentX += (int)FemaleDiameterXPixels + PartMargin;
|
|
currentY = startY + (int) FemaleDiameterYPixels / 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool addPart(decimal step)
|
|
{
|
|
decimal millimeters = Math.Round(_femaleHoleDiameter + step, 2);
|
|
if (millimeters <= 0) return false;
|
|
int xPixels = (int)(millimeters * Xppmm);
|
|
int yPixels = (int)(millimeters * Yppmm);
|
|
Point partCenterText;
|
|
|
|
if (_fuseParts)
|
|
{
|
|
if (xPixels >= FemaleHoleDiameterXPixels || yPixels >= FemaleHoleDiameterYPixels) return false;
|
|
if (currentX + FemaleDiameterXPixels + _leftRightMargin >= SlicerFile.Resolution.Width)
|
|
{
|
|
currentX = startX;
|
|
currentY += (int)FemaleDiameterYPixels + PartMargin;
|
|
}
|
|
|
|
if (currentY + FemaleDiameterYPixels + _topBottomMargin >= SlicerFile.Resolution.Height)
|
|
{
|
|
return false; // Insufficient size
|
|
}
|
|
|
|
int halfDiameterX = (int)(FemaleDiameterXPixels / 2);
|
|
int halfDiameterY = (int)(FemaleDiameterYPixels / 2);
|
|
|
|
switch (Shape)
|
|
{
|
|
case Shapes.Circle:
|
|
CvInvoke.Circle(layer, new Point(currentX + halfDiameterX, currentY + halfDiameterY), (int)(FemaleDiameterXPixels / 2), EmguExtensions.WhiteColor, -1, lineType);
|
|
CvInvoke.Circle(layer, new Point(currentX + halfDiameterX, currentY + halfDiameterY), (int)(FemaleHoleDiameterXPixels / 2), EmguExtensions.BlackColor, -1, lineType);
|
|
CvInvoke.Circle(layer, new Point(currentX + halfDiameterX, currentY + halfDiameterY), xPixels / 2, EmguExtensions.WhiteColor, -1, lineType);
|
|
break;
|
|
case Shapes.Square:
|
|
int offsetX = (int)((FemaleDiameterXPixels - FemaleHoleDiameterXPixels) / 2);
|
|
int offsetY = (int)((FemaleDiameterYPixels - FemaleHoleDiameterYPixels) / 2);
|
|
CvInvoke.Rectangle(layer, new Rectangle(currentX, currentY, (int)FemaleDiameterXPixels, (int)FemaleDiameterXPixels), EmguExtensions.WhiteColor, -1, lineType);
|
|
CvInvoke.Rectangle(layer, new Rectangle(currentX + offsetX, currentY + offsetY, (int)FemaleHoleDiameterXPixels, (int)FemaleHoleDiameterYPixels), EmguExtensions.BlackColor, -1, lineType);
|
|
offsetX = (int)((FemaleDiameterXPixels - xPixels) / 2);
|
|
offsetY = (int)((FemaleDiameterYPixels - yPixels) / 2);
|
|
CvInvoke.Rectangle(layer, new Rectangle(currentX + offsetX, currentY + offsetY, xPixels, yPixels), EmguExtensions.WhiteColor, -1, lineType);
|
|
break;
|
|
}
|
|
|
|
partCenterText = new Point(currentX + halfDiameterX - 60, currentY + halfDiameterY + 10);
|
|
currentX += (int)FemaleDiameterXPixels + PartMargin;
|
|
}
|
|
else
|
|
{
|
|
if (currentX + xPixels + _leftRightMargin >= SlicerFile.Resolution.Width)
|
|
{
|
|
currentX = startX;
|
|
currentY += yPixels + PartMargin;
|
|
}
|
|
|
|
if (currentY + yPixels + _topBottomMargin >= SlicerFile.Resolution.Height)
|
|
{
|
|
return false; // Insufficient size
|
|
}
|
|
|
|
int halfDiameterX = xPixels / 2;
|
|
int halfDiameterY = yPixels / 2;
|
|
|
|
switch (Shape)
|
|
{
|
|
case Shapes.Circle:
|
|
CvInvoke.Circle(layer, new Point(currentX + halfDiameterX, currentY + halfDiameterY), halfDiameterX, EmguExtensions.WhiteColor, -1, lineType);
|
|
break;
|
|
case Shapes.Square:
|
|
CvInvoke.Rectangle(layer, new Rectangle(currentX, currentY, xPixels, yPixels), EmguExtensions.WhiteColor, -1, lineType);
|
|
break;
|
|
}
|
|
|
|
partCenterText = new Point(currentX + halfDiameterX - 60, currentY + halfDiameterY + 10);
|
|
|
|
currentX += xPixels + PartMargin;
|
|
}
|
|
|
|
pointTextList.Add(new KeyValuePair<Point, string>(partCenterText, step > 0 ? $"+{step:F2}" : $"{step:F2}"));
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!_fuseParts && _outputSameDiameterPart)
|
|
{
|
|
addPart(0);
|
|
}
|
|
|
|
for (int i = 1; i <= _maleThinnerModels; i++)
|
|
{
|
|
var step = _maleThinnerOffset + _maleThinnerStep * i;
|
|
if (!addPart(step)) break;
|
|
}
|
|
|
|
for (int i = 1; i <= _maleThickerModels; i++)
|
|
{
|
|
var step = _maleThickerOffset + _maleThickerStep * i;
|
|
if (!addPart(step)) break;
|
|
}
|
|
|
|
Parallel.For(0, layers.Length, CoreSettings.ParallelOptions, layerIndex =>
|
|
//for (var i = 0; i < layers.Length; i++)
|
|
{
|
|
layers[layerIndex] = layer.Clone();
|
|
});
|
|
|
|
if (_erodeBottomIterations > 0)
|
|
{
|
|
Parallel.For(0, _bottomLayers, CoreSettings.ParallelOptions, layerIndex =>
|
|
{
|
|
CvInvoke.Erode(layers[layerIndex], layers[layerIndex], kernel, EmguExtensions.AnchorCenter, _erodeBottomIterations, BorderType.Reflect101, default);
|
|
});
|
|
}
|
|
|
|
if (_chamferLayers > 0)
|
|
{
|
|
Parallel.For(0, _chamferLayers, CoreSettings.ParallelOptions, layerIndexOffset =>
|
|
{
|
|
var iteration = _chamferLayers - layerIndexOffset;
|
|
CvInvoke.Erode(layers[layerIndexOffset], layers[layerIndexOffset], kernel, EmguExtensions.AnchorCenter, iteration, BorderType.Reflect101, default);
|
|
|
|
var layerIndex = layers.Length - 1 - layerIndexOffset;
|
|
CvInvoke.Erode(layers[layerIndex], layers[layerIndex], kernel, EmguExtensions.AnchorCenter, iteration, BorderType.Reflect101, default);
|
|
});
|
|
/*byte iterations = _chamferLayers;
|
|
var layerIndex = 0;
|
|
for (; layerIndex < LayerCount && iterations > 0; layerIndex++)
|
|
{
|
|
CvInvoke.Erode(layers[layerIndex], layers[layerIndex], kernel, anchor, iterations--, BorderType.Reflect101, default);
|
|
}
|
|
|
|
iterations = _chamferLayers;
|
|
for (int i = (int) (LayerCount - 1); i >= 0 && i > layerIndex && iterations > 0; i--)
|
|
{
|
|
CvInvoke.Erode(layers[i], layers[i], kernel, anchor, iterations--, BorderType.Reflect101, default);
|
|
}*/
|
|
}
|
|
|
|
Parallel.For(Math.Max(0u, LayerCount - 15), LayerCount, CoreSettings.ParallelOptions, layerIndex =>
|
|
{
|
|
foreach (var keyValuePair in pointTextList)
|
|
{
|
|
CvInvoke.PutText(layers[layerIndex], keyValuePair.Value, keyValuePair.Key, fontFace, fontScale, EmguExtensions.BlackColor, fontThickness, lineType);
|
|
}
|
|
});
|
|
|
|
if (_mirrorOutput)
|
|
{
|
|
var flip = SlicerFile.DisplayMirror;
|
|
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
|
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
|
|
}
|
|
|
|
return layers;
|
|
}
|
|
|
|
public Mat GetThumbnail()
|
|
{
|
|
Mat thumbnail = EmguExtensions.InitMat(new Size(400, 200), 3);
|
|
var fontFace = FontFace.HersheyDuplex;
|
|
var fontScale = 1;
|
|
var fontThickness = 2;
|
|
const byte xSpacing = 45;
|
|
const byte ySpacing = 45;
|
|
CvInvoke.PutText(thumbnail, "UVtools", new Point(140, 35), fontFace, fontScale, new MCvScalar(255, 27, 245), fontThickness + 1);
|
|
CvInvoke.Line(thumbnail, new Point(xSpacing, 0), new Point(xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3);
|
|
CvInvoke.Line(thumbnail, new Point(xSpacing, ySpacing + 5), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3);
|
|
CvInvoke.Line(thumbnail, new Point(thumbnail.Width - xSpacing, 0), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3);
|
|
CvInvoke.PutText(thumbnail, "Tolerance Cal.", new Point(xSpacing, ySpacing * 2), fontFace, fontScale, new MCvScalar(0, 255, 255), fontThickness);
|
|
CvInvoke.PutText(thumbnail, $"{Microns}um @ {BottomExposure}s/{NormalExposure}s", new Point(xSpacing, ySpacing * 3), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
|
|
CvInvoke.PutText(thumbnail, $"Objects: {OutputObjects}", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
|
|
|
|
/*thumbnail.SetTo(EmguExtensions.Black3Byte);
|
|
|
|
CvInvoke.Circle(thumbnail, new Point(400/2, 200/2), 200/2, EmguExtensions.White3Byte, -1);
|
|
for (int angle = 0; angle < 360; angle+=20)
|
|
{
|
|
CvInvoke.Line(thumbnail, new Point(400 / 2, 200 / 2), new Point((int)(400 / 2 + 100 * Math.Cos(angle * Math.PI / 180)), (int)(200 / 2 + 100 * Math.Sin(angle * Math.PI / 180))), new MCvScalar(255, 27, 245), 3);
|
|
}
|
|
|
|
thumbnail.Save("D:\\Thumbnail.png");*/
|
|
return thumbnail;
|
|
}
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
progress.ItemCount = LayerCount;
|
|
|
|
var newLayers = new Layer[LayerCount];
|
|
|
|
var layers = GetLayers();
|
|
|
|
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
|
|
{
|
|
newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], SlicerFile) {IsModified = true};
|
|
layers[layerIndex].Dispose();
|
|
progress.LockAndIncrement();
|
|
});
|
|
|
|
if (SlicerFile.ThumbnailsCount > 0)
|
|
SlicerFile.SetThumbnails(GetThumbnail());
|
|
|
|
SlicerFile.SuppressRebuildPropertiesWork(() =>
|
|
{
|
|
SlicerFile.LayerHeight = (float)LayerHeight;
|
|
SlicerFile.BottomExposureTime = (float)BottomExposure;
|
|
SlicerFile.ExposureTime = (float)NormalExposure;
|
|
SlicerFile.BottomLayerCount = BottomLayers;
|
|
SlicerFile.TransitionLayerCount = 0;
|
|
SlicerFile.Layers = newLayers;
|
|
}, true);
|
|
|
|
var moveOp = new OperationMove(SlicerFile);
|
|
moveOp.Execute(progress);
|
|
|
|
|
|
return !progress.Token.IsCancellationRequested;
|
|
}
|
|
|
|
#endregion
|
|
} |