mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
ea4f7e0400
- **UI:** - (Improvement) Layer navigation load time by parallel `Mat` to `Bitmap` conversion - (Improvement) Allow to show exceptions without the stack trace and detailed trigger action by using the `MessageExceiption` (#644) - (Improvement) Allow progress to have and display a detailed log (#644) - (Improvement) Convert format to another with multiple versions will now only show the possible versions for the extension - **Suggestion - Wait time before cure:** - (Improvement) Set the first wait time based on first valid layer mass rather than use the fixed limit - (Improvement) Set zero time to empty and dummy layers - (Improvement) When creating the dummy layer also increment the bottom layer count as the created layer count as one - **PCB Exposure:** - (Add) Excellon Drill Format (drl) to cut off holes (Implementation may lack some advanced features, please confirm the result) (#646) - (Fix) Arc (G03) with negative offsets (I-/J-) was not drawing the shape correctly - (Fix) Implement the rotation for the outline primitive (#645) - **File formats:** - (Improvement) Formats now sanitize the selected version before encode given the file extension, if version is out of range it will force the last known version - (Fix) CBDDLP: Remove a table from the file that might cause layer corruption - (Add) Operations - `AfterCompleteReport` property: Gets or sets an report to show to the user after complete the operation with success - (Improvement) Suggestion - Wait time after cure: Set zero time to empty and dummy layers - (Improvement) Slight improvement on the contour intersection check, yields better performance on resin and suction cup detection - (Improvement) Allow to trigger message boxes from operations and scripts (#644) - (Upgrade) .NET from 6.0.12 to 6.0.13
428 lines
16 KiB
C#
428 lines
16 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 UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
using UVtools.Core.Layers;
|
|
|
|
namespace UVtools.Core.Operations;
|
|
|
|
|
|
public sealed class OperationCalibrateBloomingEffect : Operation
|
|
{
|
|
#region Members
|
|
private decimal _layerHeight;
|
|
private ushort _bottomLayers = 3;
|
|
private decimal _bottomExposure;
|
|
private decimal _normalExposure;
|
|
private ushort _leftRightMargin = 200;
|
|
private ushort _topBottomMargin = 200;
|
|
private decimal _waitTimeBeforeCureStart;
|
|
private decimal _waitTimeBeforeCureIncrement = 1;
|
|
private byte _objectCount = 15;
|
|
private ushort _objectDiameter = 400;
|
|
private decimal _objectHeight = 5;
|
|
private ushort _objectMargin = 20;
|
|
private bool _mirrorOutput;
|
|
|
|
#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-sun";
|
|
public override string Title => "Blooming effect";
|
|
public override string Description =>
|
|
"Generates test models with various strategies and increments to measure the blooming effect.\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 bloom effect test?";
|
|
|
|
public override string ProgressTitle =>
|
|
$"Generating the bloom effect test";
|
|
|
|
public override string ProgressAction => "Generated";
|
|
|
|
public override string? ValidateSpawn()
|
|
{
|
|
if (!SlicerFile.CanUseLayerPositionZ)
|
|
{
|
|
return $"{NotSupportedMessage}\nReason: Can't use more than one layer per same position.";
|
|
}
|
|
|
|
if (SlicerFile is {CanUseLayerWaitTimeBeforeCure: false, CanUseLayerLightOffDelay: false})
|
|
{
|
|
return $"{NotSupportedMessage}\nReason: No per layer wait time before cure capabilities.";
|
|
}
|
|
|
|
|
|
return null;
|
|
}
|
|
|
|
public override string? ValidateInternally()
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (SlicerFile.ResolutionX - _leftRightMargin * 2 <= 0)
|
|
sb.AppendLine("The top/bottom margin is too big, it overlaps the screen resolution.");
|
|
|
|
if (SlicerFile.ResolutionY - _topBottomMargin * 2 <= 0)
|
|
sb.AppendLine("The top/bottom margin is too big, it overlaps the screen resolution.");
|
|
|
|
if (_leftRightMargin + _objectDiameter > SlicerFile.ResolutionX - _leftRightMargin)
|
|
sb.AppendLine("The top/bottom margin or object diameter is too big, it overlaps the screen resolution.");
|
|
|
|
if (_topBottomMargin + _objectDiameter > SlicerFile.ResolutionY - _topBottomMargin)
|
|
sb.AppendLine("The top/bottom margin or object diameter is too big, it overlaps the screen resolution.");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var result = $"[Layer Height: {_layerHeight}] " +
|
|
$"[Bottom layers: {_bottomLayers}] " +
|
|
$"[Exposure: {_bottomExposure}/{_normalExposure}s]" +
|
|
$"[Wait time: {_waitTimeBeforeCureStart}s] [Increment: {_waitTimeBeforeCureIncrement}s] " +
|
|
$"[Objects: {_objectCount}] [Height: {_objectHeight}mm] [Diameter: {_objectDiameter}px]";
|
|
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public decimal LayerHeight
|
|
{
|
|
get => _layerHeight;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
|
|
RaisePropertyChanged(nameof(BottomHeight));
|
|
}
|
|
}
|
|
|
|
public ushort BottomLayers
|
|
{
|
|
get => _bottomLayers;
|
|
set => RaiseAndSetIfChanged(ref _bottomLayers, value);
|
|
}
|
|
|
|
public ushort Microns => (ushort) (LayerHeight * 1000);
|
|
|
|
public decimal BottomHeight => 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 ushort LeftRightMargin
|
|
{
|
|
get => _leftRightMargin;
|
|
set => RaiseAndSetIfChanged(ref _leftRightMargin, value);
|
|
}
|
|
|
|
public ushort MaxLeftRightMargin => (ushort)((SlicerFile.ResolutionX - 100) / 2);
|
|
|
|
public ushort TopBottomMargin
|
|
{
|
|
get => _topBottomMargin;
|
|
set => RaiseAndSetIfChanged(ref _topBottomMargin, value);
|
|
}
|
|
|
|
public ushort MaxTopBottomMargin => (ushort) ((SlicerFile.ResolutionY - 100) / 2);
|
|
|
|
public decimal WaitTimeBeforeCureStart
|
|
{
|
|
get => _waitTimeBeforeCureStart;
|
|
set => RaiseAndSetIfChanged(ref _waitTimeBeforeCureStart, Math.Round(Math.Max(0, value), 2));
|
|
}
|
|
|
|
public decimal WaitTimeBeforeCureIncrement
|
|
{
|
|
get => _waitTimeBeforeCureIncrement;
|
|
set => RaiseAndSetIfChanged(ref _waitTimeBeforeCureIncrement, Math.Round(Math.Max(0.05m, value), 2));
|
|
}
|
|
|
|
public byte ObjectCount
|
|
{
|
|
get => _objectCount;
|
|
set => RaiseAndSetIfChanged(ref _objectCount, Math.Max((byte)1, value));
|
|
}
|
|
|
|
public decimal MaximumWaiTimeBeforeCure => Math.Round(_waitTimeBeforeCureStart + _waitTimeBeforeCureIncrement * _objectCount, 2);
|
|
|
|
public ushort ObjectDiameter
|
|
{
|
|
get => _objectDiameter;
|
|
set => RaiseAndSetIfChanged(ref _objectDiameter, Math.Max((ushort)20, value));
|
|
}
|
|
|
|
public decimal ObjectHeight
|
|
{
|
|
get => _objectHeight;
|
|
set => RaiseAndSetIfChanged(ref _objectHeight, value);
|
|
}
|
|
|
|
public ushort ObjectMargin
|
|
{
|
|
get => _objectMargin;
|
|
set => RaiseAndSetIfChanged(ref _objectMargin, value);
|
|
}
|
|
|
|
public bool MirrorOutput
|
|
{
|
|
get => _mirrorOutput;
|
|
set => RaiseAndSetIfChanged(ref _mirrorOutput, value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public OperationCalibrateBloomingEffect() { }
|
|
|
|
public OperationCalibrateBloomingEffect(FileFormat slicerFile) : base(slicerFile)
|
|
{ }
|
|
|
|
public override void InitWithSlicerFile()
|
|
{
|
|
base.InitWithSlicerFile();
|
|
if(_layerHeight <= 0) _layerHeight = (decimal)SlicerFile.LayerHeight;
|
|
if(_bottomExposure <= 0) _bottomExposure = (decimal)SlicerFile.BottomExposureTime;
|
|
if(_normalExposure <= 0) _normalExposure = (decimal)SlicerFile.ExposureTime;
|
|
_mirrorOutput = SlicerFile.DisplayMirror != FlipDirection.None;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Equality
|
|
|
|
|
|
private bool Equals(OperationCalibrateBloomingEffect other)
|
|
{
|
|
return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _leftRightMargin == other._leftRightMargin && _topBottomMargin == other._topBottomMargin && _waitTimeBeforeCureStart == other._waitTimeBeforeCureStart && _waitTimeBeforeCureIncrement == other._waitTimeBeforeCureIncrement && _objectCount == other._objectCount && _objectDiameter == other._objectDiameter && _objectHeight == other._objectHeight && _objectMargin == other._objectMargin && _mirrorOutput == other._mirrorOutput;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return ReferenceEquals(this, obj) || obj is OperationCalibrateBloomingEffect 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(_leftRightMargin);
|
|
hashCode.Add(_topBottomMargin);
|
|
hashCode.Add(_waitTimeBeforeCureStart);
|
|
hashCode.Add(_waitTimeBeforeCureIncrement);
|
|
hashCode.Add(_objectCount);
|
|
hashCode.Add(_objectDiameter);
|
|
hashCode.Add(_objectHeight);
|
|
hashCode.Add(_objectMargin);
|
|
hashCode.Add(_mirrorOutput);
|
|
return hashCode.ToHashCode();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Gets the layers
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Mat GetLayerPreview()
|
|
{
|
|
var mat = SlicerFile.CreateMat();
|
|
|
|
uint currentX = _leftRightMargin;
|
|
uint currentY = _topBottomMargin;
|
|
uint maxWidth = SlicerFile.ResolutionX - _leftRightMargin;
|
|
uint maxHeight = SlicerFile.ResolutionY - _topBottomMargin;
|
|
var maxWaitTime = MaximumWaiTimeBeforeCure;
|
|
|
|
for (decimal waitTime = _waitTimeBeforeCureStart; waitTime <= maxWaitTime; waitTime += _waitTimeBeforeCureIncrement)
|
|
{
|
|
if (currentX + _objectDiameter > maxWidth)
|
|
{
|
|
currentX = _leftRightMargin;
|
|
currentY += (uint)(_objectDiameter + _objectMargin);
|
|
if (currentY + _objectDiameter > maxHeight) break;
|
|
}
|
|
|
|
waitTime = Math.Round(waitTime, 2);
|
|
|
|
CvInvoke.Rectangle(mat, new Rectangle((int)currentX, (int)currentY, _objectDiameter, _objectDiameter), EmguExtensions.WhiteColor, -1);
|
|
mat.PutTextExtended($"E: {_bottomExposure}s/{_normalExposure}s\nW: {waitTime}s", new Point((int) currentX+20, (int) currentY + _objectDiameter / 2), FontFace.HersheyDuplex, 2.0, EmguExtensions.BlackColor, 2, 10);
|
|
currentX += (uint)(_objectDiameter + _objectMargin);
|
|
}
|
|
|
|
return mat;
|
|
}
|
|
|
|
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, "Bloom Effect 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, $"Wait: {_waitTimeBeforeCureStart}s/+{_waitTimeBeforeCureIncrement}s", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
progress.ItemCount = 1;
|
|
|
|
var newLayers = new List<Layer>();
|
|
|
|
if (SlicerFile.ThumbnailsCount > 0)
|
|
SlicerFile.SetThumbnails(GetThumbnail());
|
|
|
|
var maxWaitTime = MaximumWaiTimeBeforeCure;
|
|
var flip = SlicerFile.DisplayMirror;
|
|
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
|
|
|
SlicerFile.SuppressRebuildPropertiesWork(() =>
|
|
{
|
|
float currentPosition = (float)_layerHeight;
|
|
SlicerFile.LayerHeight = currentPosition;
|
|
SlicerFile.BottomExposureTime = (float)_bottomExposure;
|
|
SlicerFile.ExposureTime = (float)_normalExposure;
|
|
SlicerFile.BottomLayerCount = _bottomLayers;
|
|
SlicerFile.TransitionLayerCount = 0;
|
|
SlicerFile.BottomLightOffDelay = 0;
|
|
SlicerFile.LightOffDelay = 0;
|
|
SlicerFile.SetBottomWaitTimeBeforeCureOrLightOffDelay((float)_waitTimeBeforeCureStart);
|
|
SlicerFile.SetNormalWaitTimeBeforeCureOrLightOffDelay((float)(_waitTimeBeforeCureStart + _waitTimeBeforeCureIncrement * _objectCount));
|
|
|
|
|
|
uint currentX = _leftRightMargin;
|
|
uint currentY = _topBottomMargin;
|
|
uint maxWidth = SlicerFile.ResolutionX - _leftRightMargin;
|
|
uint maxHeight = SlicerFile.ResolutionY - _topBottomMargin;
|
|
|
|
for (decimal waitTime = _waitTimeBeforeCureStart; waitTime <= maxWaitTime; waitTime += _waitTimeBeforeCureIncrement)
|
|
{
|
|
if (currentX + _objectDiameter > maxWidth)
|
|
{
|
|
currentX = _leftRightMargin;
|
|
currentY += (uint)(_objectDiameter + _objectMargin);
|
|
if(currentY + _objectDiameter > maxHeight) break;
|
|
}
|
|
|
|
waitTime = Math.Round(waitTime, 2);
|
|
|
|
using var mat = SlicerFile.CreateMat();
|
|
CvInvoke.Rectangle(mat, new Rectangle((int)currentX, (int)currentY, _objectDiameter, _objectDiameter), EmguExtensions.WhiteColor, -1);
|
|
if (_mirrorOutput) CvInvoke.Flip(mat, mat, (FlipType)flip);
|
|
var layer = new Layer(mat, SlicerFile)
|
|
{
|
|
PositionZ = currentPosition
|
|
};
|
|
layer.SetWaitTimeBeforeCureOrLightOffDelay((float) waitTime);
|
|
newLayers.Add(layer);
|
|
|
|
currentX += (uint)(_objectDiameter + _objectMargin);
|
|
}
|
|
|
|
var objects = newLayers.Count;
|
|
int heightLayers = (int)Math.Ceiling(_objectHeight / (decimal)SlicerFile.LayerHeight);
|
|
|
|
for (int h = 0; h < heightLayers; h++)
|
|
{
|
|
currentPosition += (float)_layerHeight;
|
|
for (int i = 0; i < objects; i++)
|
|
{
|
|
var layer = newLayers[i].Clone();
|
|
layer.PositionZ = currentPosition;
|
|
newLayers.Add(layer);
|
|
}
|
|
}
|
|
|
|
using var textMat = SlicerFile.CreateMat();
|
|
currentX = _leftRightMargin;
|
|
currentY = _topBottomMargin;
|
|
for (decimal waitTime = _waitTimeBeforeCureStart; waitTime <= maxWaitTime; waitTime += _waitTimeBeforeCureIncrement)
|
|
{
|
|
if (currentX + _objectDiameter > maxWidth)
|
|
{
|
|
currentX = _leftRightMargin;
|
|
currentY += (uint)(_objectDiameter + _objectMargin);
|
|
if (currentY > maxHeight) break;
|
|
}
|
|
|
|
waitTime = Math.Round(waitTime, 2);
|
|
|
|
textMat.PutTextExtended($"E: {_bottomExposure}s/{_normalExposure}s\nW: {waitTime}s", new Point((int)currentX + 20, (int)currentY + _objectDiameter / 2),
|
|
FontFace.HersheyDuplex, 2.0, EmguExtensions.WhiteColor, 2, 10);
|
|
currentX += (uint)(_objectDiameter + _objectMargin);
|
|
}
|
|
|
|
if (_mirrorOutput) CvInvoke.Flip(textMat, textMat, (FlipType) flip);
|
|
|
|
var textLayerCount = 1 / _layerHeight;
|
|
for (int i = 0; i < textLayerCount; i++)
|
|
{
|
|
currentPosition += (float)_layerHeight;
|
|
var layer = new Layer(textMat, SlicerFile)
|
|
{
|
|
PositionZ = currentPosition
|
|
};
|
|
newLayers.Add(layer);
|
|
}
|
|
|
|
|
|
SlicerFile.Layers = newLayers.ToArray();
|
|
});
|
|
|
|
|
|
progress++;
|
|
|
|
|
|
return !progress.Token.IsCancellationRequested;
|
|
}
|
|
|
|
#endregion
|
|
} |