mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
22897ea389
- **Benchmark:**
- (Add) PNG, GZip, Deflate and LZ4 compress tests
- (Change) Test against a known image instead of random noise
- (Change) Single-thread tests from 100 to 200 and multi-thread tests from 1000 to 5000
- (Improvement) Same image instance is shared between tests instead of create new per test
- (Fix) Encode typo
- **Core:**
- (Add) Layer compression method: Allow to choose the compression method for layer image
- **PNG:** Compression=High Speed=Slow (Use with low RAM)
- **GZip:** Compression=Medium Speed=Medium (Optimal)
- **Deflate:** Compression=Medium Speed=Medium (Optimal)
- **LZ4:** Compression=Low Speed=Fast (Use with high RAM)
- (Improvement) Better handling on cancel operations and more immediate response
- (Fix) Extract: Zip Slip Vulnerability (CWE-22)
- **File formats:**
- (Improvement) Better handling of encode/decoding layers from zip files
- (Fix) ZCode: Canceling the file load can trigger an error
- (Fix) VDA: Unable to open vda zip files
- **Tools:**
- (Improvement) Allow operations to be aware of ROI and Masks before execution (#436)
- (Improvement) Scripting: Allow save and load profiles (#436)
- (Fix) Adjust layer height: When using the Offset type the last layer in the range was not taken in account (#435)
- **UI:**
- (Improvement) Allow layer zoom levels of 0.1x and 64x but constrain minimum zoom to the level of image fit
- (Improvement) Update change log now shows with markdown style and more readable
- (Fix) Windows MSI upgrade to this version (#432)
- (Fix) Auto-updater for Mac ARM, was downloading x64 instead
2277 lines
95 KiB
C#
2277 lines
95 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 Emgu.CV.Util;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
using UVtools.Core.Layers;
|
|
using UVtools.Core.Objects;
|
|
|
|
namespace UVtools.Core.Operations;
|
|
|
|
[Serializable]
|
|
public sealed class OperationCalibrateExposureFinder : Operation
|
|
{
|
|
#region Enums
|
|
public enum CalibrateExposureFinderShapes : byte
|
|
{
|
|
Square,
|
|
Circle
|
|
}
|
|
public static Array ShapesItems => Enum.GetValues(typeof(CalibrateExposureFinderShapes));
|
|
|
|
public enum CalibrateExposureFinderMeasures : byte
|
|
{
|
|
Pixels,
|
|
Millimeters,
|
|
}
|
|
|
|
public static Array MeasuresItems => Enum.GetValues(typeof(CalibrateExposureFinderMeasures));
|
|
|
|
public enum CalibrateExposureFinderMultipleBrightnessExcludeFrom : byte
|
|
{
|
|
None,
|
|
Bottom,
|
|
BottomAndBase
|
|
}
|
|
public static Array MultipleBrightnessExcludeFromItems => Enum.GetValues(typeof(CalibrateExposureFinderMultipleBrightnessExcludeFrom));
|
|
|
|
public enum CalibrateExposureFinderExposureGenTypes : byte
|
|
{
|
|
Linear,
|
|
Multiplier
|
|
}
|
|
|
|
public static Array ExposureGenTypeItems => Enum.GetValues(typeof(CalibrateExposureFinderExposureGenTypes));
|
|
|
|
public enum CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes : byte
|
|
{
|
|
[Description("Iterative exposure: Print each base layer at it own exposure time")]
|
|
Iterative,
|
|
|
|
[Description("Lowest exposure: Base layers will print at the lowest defined exposure time")]
|
|
UseLowest,
|
|
|
|
[Description("Middle exposure: Base layers will print at the middle defined exposure time")]
|
|
UseMiddle,
|
|
|
|
[Description("Highest exposure: Base layers will print at the highest defined exposure time")]
|
|
UseHighest,
|
|
|
|
[Description("Custom exposure: Base layers will print at a custom defined exposure time")]
|
|
Custom
|
|
}
|
|
#endregion
|
|
|
|
#region Subclasses
|
|
|
|
public sealed class BullsEyeCircle
|
|
{
|
|
public ushort Diameter { get; set; }
|
|
public ushort Radius => (ushort) (Diameter / 2);
|
|
public ushort Thickness { get; set; } = 10;
|
|
|
|
public BullsEyeCircle() {}
|
|
|
|
public BullsEyeCircle(ushort diameter, ushort thickness)
|
|
{
|
|
Diameter = diameter;
|
|
Thickness = thickness;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Constants
|
|
|
|
const byte TextMarkingSpacing = 60;
|
|
const byte TextMarkingLineBreak = 30;
|
|
const FontFace TextMarkingFontFace = Emgu.CV.CvEnum.FontFace.HersheyDuplex;
|
|
const byte TextMarkingStartX = 10;
|
|
//const byte TextStartY = 50;
|
|
const double TextMarkingScale = 0.8;
|
|
const byte TextMarkingThickness = 2;
|
|
|
|
#endregion
|
|
|
|
#region Members
|
|
private decimal _displayWidth;
|
|
private decimal _displayHeight;
|
|
private decimal _layerHeight;
|
|
private ushort _bottomLayers;
|
|
private decimal _bottomExposure;
|
|
private decimal _normalExposure;
|
|
private decimal _topBottomMargin = 5;
|
|
private decimal _leftRightMargin = 10;
|
|
private byte _chamferLayers = 0;
|
|
private byte _erodeBottomIterations = 0;
|
|
private decimal _partMargin = 0;
|
|
private bool _enableAntiAliasing = false;
|
|
private bool _mirrorOutput;
|
|
private decimal _baseHeight = 1;
|
|
private decimal _featuresHeight = 1;
|
|
private decimal _featuresMargin = 2m;
|
|
|
|
private ushort _staircaseThicknessPx = 40;
|
|
private decimal _staircaseThicknessMm = 2;
|
|
|
|
private bool _holesEnabled = false;
|
|
private CalibrateExposureFinderShapes _holeShape = CalibrateExposureFinderShapes.Square;
|
|
private CalibrateExposureFinderMeasures _unitOfMeasure = CalibrateExposureFinderMeasures.Pixels;
|
|
private string _holeDiametersPx = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11";
|
|
private string _holeDiametersMm = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2";
|
|
|
|
private bool _barsEnabled = true;
|
|
private decimal _barSpacing = 1.5m;
|
|
private decimal _barLength = 4;
|
|
private sbyte _barVerticalSplitter = 0;
|
|
private byte _barFenceThickness = 10;
|
|
private sbyte _barFenceOffset = 4;
|
|
private string _barThicknessesPx = "4, 6, 8, 60"; //"4, 6, 8, 10, 12, 14, 16, 18, 20";
|
|
private string _barThicknessesMm = "0.2, 0.3, 0.4, 3"; //"0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.2";
|
|
|
|
private bool _textEnabled = true;
|
|
private FontFace _textFont = TextMarkingFontFace;
|
|
private double _textScale = 1;
|
|
private byte _textThickness = 2;
|
|
private string _text = "ABHJQRWZ%&#"; //"ABGHJKLMQRSTUVWXZ%&#";
|
|
|
|
private bool _multipleBrightness;
|
|
private CalibrateExposureFinderMultipleBrightnessExcludeFrom _multipleBrightnessExcludeFrom = CalibrateExposureFinderMultipleBrightnessExcludeFrom.BottomAndBase;
|
|
private string _multipleBrightnessValues = null!;
|
|
private decimal _multipleBrightnessGenExposureTime;
|
|
private byte _multipleBrightnessGenEmulatedAALevel = FileFormat.MaximumAntiAliasing;
|
|
private byte _multipleBrightnessGenExposureFractions = 8;
|
|
|
|
private bool _multipleLayerHeight;
|
|
private decimal _multipleLayerHeightMaximum = 0.1m;
|
|
private decimal _multipleLayerHeightStep = 0.01m;
|
|
|
|
private CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes _multipleExposuresBaseLayersPrintMode;
|
|
private decimal _multipleExposuresBaseLayersCustomExposure;
|
|
private bool _differentSettingsForSamePositionedLayers;
|
|
private bool _samePositionedLayersLiftHeightEnabled = true;
|
|
private decimal _samePositionedLayersLiftHeight;
|
|
private bool _samePositionedLayersWaitTimeBeforeCureEnabled = true;
|
|
private decimal _samePositionedLayersWaitTimeBeforeCure;
|
|
private bool _multipleExposures;
|
|
private CalibrateExposureFinderExposureGenTypes _exposureGenType = CalibrateExposureFinderExposureGenTypes.Linear;
|
|
private bool _exposureGenIgnoreBaseExposure;
|
|
private decimal _exposureGenBottomStep = 0;
|
|
private decimal _exposureGenNormalStep = 0.2m;
|
|
private byte _exposureGenTests = 4;
|
|
private decimal _exposureGenManualLayerHeight;
|
|
private decimal _exposureGenManualBottom;
|
|
private decimal _exposureGenManualNormal;
|
|
private RangeObservableCollection<ExposureItem> _exposureTable = new();
|
|
|
|
private bool _bullsEyeEnabled = true;
|
|
private string _bullsEyeConfigurationPx = "26:5, 60:10, 116:15, 190:20";
|
|
private string _bullsEyeConfigurationMm = "1.3:0.25, 3:0.5, 5.8:0.75, 9.5:1";
|
|
private bool _bullsEyeInvertQuadrants = true;
|
|
|
|
private bool _counterTrianglesEnabled = true;
|
|
private sbyte _counterTrianglesTipOffset = 3;
|
|
private bool _counterTrianglesFence = false;
|
|
|
|
private bool _patternModel;
|
|
private byte _bullsEyeFenceThickness = 10;
|
|
private sbyte _bullsEyeFenceOffset;
|
|
private bool _patternModelGlueBottomLayers = true;
|
|
private bool _patternModelTextEnabled = true;
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
public override bool CanROI => false;
|
|
|
|
public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None;
|
|
public override string IconClass => "mdi-timer-cog";
|
|
public override string Title => "Exposure time finder";
|
|
public override string Description =>
|
|
"Generates test models with various strategies and increments to verify the best exposure time for a given layer height.\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 exposure time finder test?";
|
|
|
|
public override string ProgressTitle =>
|
|
$"Generating the exposure time finder test";
|
|
|
|
public override string ProgressAction => "Generated layers";
|
|
|
|
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 (_chamferLayers * _layerHeight > _baseHeight)
|
|
{
|
|
sb.AppendLine("The chamfer can't be higher than the base height, lower the chamfer layer count.");
|
|
}
|
|
|
|
if (_multipleExposures)
|
|
{
|
|
var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight;
|
|
for (decimal layerHeight = _layerHeight;
|
|
layerHeight <= endLayerHeight;
|
|
layerHeight += _multipleLayerHeightStep)
|
|
{
|
|
bool found = false;
|
|
foreach (var exposureItem in _exposureTable)
|
|
{
|
|
if (exposureItem.LayerHeight == layerHeight && exposureItem.IsValid)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found)
|
|
sb.AppendLine($"[ME]: The {Layer.ShowHeight(layerHeight)}mm layer height have no set exposure(s).");
|
|
}
|
|
}
|
|
|
|
if (_multipleBrightness)
|
|
{
|
|
var brightnessValues = MultipleBrightnessValuesArray;
|
|
if (brightnessValues.Length == 0)
|
|
{
|
|
sb.AppendLine($"Multiple brightness tests are enabled but no valid values are set, use from 1 to 255.");
|
|
}
|
|
else
|
|
{
|
|
if (SlicerFile.IsAntiAliasingEmulated)
|
|
{
|
|
if (brightnessValues.Length > 16)
|
|
{
|
|
sb.AppendLine(
|
|
"[ME] This format uses time fractions to emulate AntiAliasing, only up 16 levels of greys/brightness are permitted.");
|
|
}
|
|
else
|
|
{
|
|
/*byte aalevel = brightnessValues.Length switch
|
|
{
|
|
<= 2 => 2,
|
|
<= 4 => 4,
|
|
<= 8 => 8,
|
|
<= 16 => 16,
|
|
_ => 2
|
|
};*/
|
|
|
|
var increment = 255f / _multipleBrightnessGenEmulatedAALevel;
|
|
|
|
byte[] validAA = new byte[_multipleBrightnessGenEmulatedAALevel];
|
|
|
|
for (byte frac = 0; frac < _multipleBrightnessGenEmulatedAALevel; frac++)
|
|
{
|
|
validAA[frac] = (byte)(byte.MaxValue - increment * frac);
|
|
}
|
|
|
|
string invalidAA = string.Empty;
|
|
|
|
foreach (var brightness in brightnessValues)
|
|
{
|
|
if (!validAA.Contains(brightness))
|
|
invalidAA += $"{brightness}, ";
|
|
}
|
|
|
|
invalidAA = invalidAA.Trim().TrimEnd(',');
|
|
|
|
if (!string.IsNullOrWhiteSpace(invalidAA))
|
|
{
|
|
sb.AppendLine($"[ME] This format uses time fractions to emulate AntiAliasing, only some levels greys/brightness are permitted, and everything outside that is thresholded.");
|
|
sb.AppendLine($" - your input have the following wrong levels: {invalidAA}");
|
|
sb.AppendLine($" - AntiAliasing level: {_multipleBrightnessGenEmulatedAALevel} with usable values of: {string.Join(", ", validAA)}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_patternModel)
|
|
{
|
|
if (!CanPatternModel)
|
|
{
|
|
sb.AppendLine($"Unable to pattern the loaded model within the available space.");
|
|
}
|
|
|
|
if (!_multipleBrightness && !_multipleExposures)
|
|
{
|
|
sb.AppendLine($"Pattern the loaded model requires either multiple brightness or multiple exposures to use with.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Bars.Length <= 0 && Holes.Length <= 0 && BullsEyes.Length <= 0 && TextSize.IsEmpty)
|
|
{
|
|
sb.AppendLine("No objects to output, enable at least 1 feature.");
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var result = $"[Layer Height: {_layerHeight}] " +
|
|
$"[Bottom layers: {_bottomLayers}] " +
|
|
$"[Exposure: {_bottomExposure}/{_normalExposure}] " +
|
|
$"[TB:{_topBottomMargin} LR:{_leftRightMargin} PM:{_partMargin} FM:{_featuresMargin}] " +
|
|
$"[Chamfer: {_chamferLayers}] [Erode: {_erodeBottomIterations}] " +
|
|
$"[Obj height: {_featuresHeight}] " +
|
|
$"[Holes: {Holes.Length}] [Bars: {Bars.Length}] [BE: {BullsEyes.Length}] [Text: {!string.IsNullOrWhiteSpace(_text)}]" +
|
|
$"[AA: {_enableAntiAliasing}] [Mirror: {_mirrorOutput}]";
|
|
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 Ppmm => Math.Max(Xppmm, Yppmm);
|
|
|
|
public decimal LayerHeight
|
|
{
|
|
get => _layerHeight;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
|
|
RaisePropertyChanged(nameof(BottomLayersMM));
|
|
RaisePropertyChanged(nameof(AvailableLayerHeights));
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(MultipleBrightnessTable));
|
|
}
|
|
}
|
|
|
|
public decimal NormalExposure
|
|
{
|
|
get => _normalExposure;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2))) return;
|
|
RaisePropertyChanged(nameof(MultipleBrightnessTable));
|
|
}
|
|
}
|
|
|
|
public decimal TopBottomMargin
|
|
{
|
|
get => _topBottomMargin;
|
|
set => RaiseAndSetIfChanged(ref _topBottomMargin, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal LeftRightMargin
|
|
{
|
|
get => _leftRightMargin;
|
|
set => RaiseAndSetIfChanged(ref _leftRightMargin, Math.Round(value, 2));
|
|
}
|
|
|
|
public byte ChamferLayers
|
|
{
|
|
get => _chamferLayers;
|
|
set => RaiseAndSetIfChanged(ref _chamferLayers, value);
|
|
}
|
|
|
|
public byte ErodeBottomIterations
|
|
{
|
|
get => _erodeBottomIterations;
|
|
set => RaiseAndSetIfChanged(ref _erodeBottomIterations, value);
|
|
}
|
|
|
|
public decimal PartMargin
|
|
{
|
|
get => _partMargin;
|
|
set => RaiseAndSetIfChanged(ref _partMargin, Math.Round(value, 2));
|
|
}
|
|
|
|
public bool EnableAntiAliasing
|
|
{
|
|
get => _enableAntiAliasing;
|
|
set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value);
|
|
}
|
|
|
|
public bool MirrorOutput
|
|
{
|
|
get => _mirrorOutput;
|
|
set => RaiseAndSetIfChanged(ref _mirrorOutput, value);
|
|
}
|
|
|
|
public decimal BaseHeight
|
|
{
|
|
get => _baseHeight;
|
|
set => RaiseAndSetIfChanged(ref _baseHeight, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal FeaturesHeight
|
|
{
|
|
get => _featuresHeight;
|
|
set => RaiseAndSetIfChanged(ref _featuresHeight, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal TotalHeight => _baseHeight + _featuresHeight;
|
|
|
|
public decimal FeaturesMargin
|
|
{
|
|
get => _featuresMargin;
|
|
set => RaiseAndSetIfChanged(ref _featuresMargin, Math.Round(value, 2));
|
|
}
|
|
|
|
public ushort StaircaseThicknessPx
|
|
{
|
|
get => _staircaseThicknessPx;
|
|
set => RaiseAndSetIfChanged(ref _staircaseThicknessPx, value);
|
|
}
|
|
|
|
public decimal StaircaseThicknessMm
|
|
{
|
|
get => _staircaseThicknessMm;
|
|
set => RaiseAndSetIfChanged(ref _staircaseThicknessMm, value);
|
|
}
|
|
|
|
public ushort StaircaseThickness => _unitOfMeasure == CalibrateExposureFinderMeasures.Pixels
|
|
? _staircaseThicknessPx
|
|
: (ushort)(_staircaseThicknessMm * Yppmm);
|
|
|
|
public bool CounterTrianglesEnabled
|
|
{
|
|
get => _counterTrianglesEnabled;
|
|
set => RaiseAndSetIfChanged(ref _counterTrianglesEnabled, value);
|
|
}
|
|
|
|
public sbyte CounterTrianglesTipOffset
|
|
{
|
|
get => _counterTrianglesTipOffset;
|
|
set => RaiseAndSetIfChanged(ref _counterTrianglesTipOffset, value);
|
|
}
|
|
|
|
public bool CounterTrianglesFence
|
|
{
|
|
get => _counterTrianglesFence;
|
|
set => RaiseAndSetIfChanged(ref _counterTrianglesFence, value);
|
|
}
|
|
|
|
public bool HolesEnabled
|
|
{
|
|
get => _holesEnabled;
|
|
set => RaiseAndSetIfChanged(ref _holesEnabled, value);
|
|
}
|
|
|
|
public CalibrateExposureFinderShapes HoleShape
|
|
{
|
|
get => _holeShape;
|
|
set => RaiseAndSetIfChanged(ref _holeShape, value);
|
|
}
|
|
|
|
public CalibrateExposureFinderMeasures UnitOfMeasure
|
|
{
|
|
get => _unitOfMeasure;
|
|
set
|
|
{
|
|
if (!RaiseAndSetIfChanged(ref _unitOfMeasure, value)) return;
|
|
RaisePropertyChanged(nameof(IsUnitOfMeasureMm));
|
|
}
|
|
}
|
|
|
|
public bool IsUnitOfMeasureMm => _unitOfMeasure == CalibrateExposureFinderMeasures.Millimeters;
|
|
|
|
public string HoleDiametersMm
|
|
{
|
|
get => _holeDiametersMm;
|
|
set => RaiseAndSetIfChanged(ref _holeDiametersMm, value);
|
|
}
|
|
|
|
public string HoleDiametersPx
|
|
{
|
|
get => _holeDiametersPx;
|
|
set => RaiseAndSetIfChanged(ref _holeDiametersPx, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all holes in pixels and ordered
|
|
/// </summary>
|
|
public int[] Holes
|
|
{
|
|
get
|
|
{
|
|
if (!_holesEnabled)
|
|
{
|
|
return Array.Empty<int>();
|
|
}
|
|
|
|
List<int> holes = new();
|
|
|
|
if (_unitOfMeasure == CalibrateExposureFinderMeasures.Millimeters)
|
|
{
|
|
var split = _holeDiametersMm.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var mmStr in split)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(mmStr)) continue;
|
|
if (!decimal.TryParse(mmStr, out var mm)) continue;
|
|
var mmPx = (int)(mm * Ppmm);
|
|
if (mmPx is <= 0 or > 500) continue;
|
|
if(holes.Contains(mmPx)) continue;
|
|
holes.Add(mmPx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var split = _holeDiametersPx.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var pxStr in split)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pxStr)) continue;
|
|
if (!int.TryParse(pxStr, out var px)) continue;
|
|
if (px is <= 0 or > 500) continue;
|
|
if (holes.Contains(px)) continue;
|
|
holes.Add(px);
|
|
}
|
|
}
|
|
|
|
return holes.OrderBy(pixels => pixels).ToArray();
|
|
}
|
|
}
|
|
|
|
public int GetHolesHeight(int[] holes)
|
|
{
|
|
if (holes.Length == 0) return 0;
|
|
return (int) (holes.Sum() + (holes.Length-1) * _featuresMargin * Yppmm);
|
|
}
|
|
|
|
public bool BarsEnabled
|
|
{
|
|
get => _barsEnabled;
|
|
set => RaiseAndSetIfChanged(ref _barsEnabled, value);
|
|
}
|
|
|
|
public decimal BarSpacing
|
|
{
|
|
get => _barSpacing;
|
|
set => RaiseAndSetIfChanged(ref _barSpacing, value);
|
|
}
|
|
|
|
public decimal BarLength
|
|
{
|
|
get => _barLength;
|
|
set => RaiseAndSetIfChanged(ref _barLength, value);
|
|
}
|
|
|
|
public sbyte BarVerticalSplitter
|
|
{
|
|
get => _barVerticalSplitter;
|
|
set => RaiseAndSetIfChanged(ref _barVerticalSplitter, value);
|
|
}
|
|
|
|
public byte BarFenceThickness
|
|
{
|
|
get => _barFenceThickness;
|
|
set => RaiseAndSetIfChanged(ref _barFenceThickness, value);
|
|
}
|
|
|
|
public sbyte BarFenceOffset
|
|
{
|
|
get => _barFenceOffset;
|
|
set => RaiseAndSetIfChanged(ref _barFenceOffset, value);
|
|
}
|
|
|
|
public string BarThicknessesPx
|
|
{
|
|
get => _barThicknessesPx;
|
|
set => RaiseAndSetIfChanged(ref _barThicknessesPx, value);
|
|
}
|
|
|
|
public string BarThicknessesMm
|
|
{
|
|
get => _barThicknessesMm;
|
|
set => RaiseAndSetIfChanged(ref _barThicknessesMm, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all holes in pixels and ordered
|
|
/// </summary>
|
|
public int[] Bars
|
|
{
|
|
get
|
|
{
|
|
if (!_barsEnabled)
|
|
{
|
|
return Array.Empty<int>();
|
|
}
|
|
|
|
List<int> bars = new();
|
|
|
|
if (_unitOfMeasure == CalibrateExposureFinderMeasures.Millimeters)
|
|
{
|
|
var split = _barThicknessesMm.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var mmStr in split)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(mmStr)) continue;
|
|
if (!decimal.TryParse(mmStr, out var mm)) continue;
|
|
var mmPx = (int)(mm * Yppmm);
|
|
if (mmPx is <= 0 or > 500) continue;
|
|
if (bars.Contains(mmPx)) continue;
|
|
bars.Add(mmPx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var split = _barThicknessesPx.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var pxStr in split)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pxStr)) continue;
|
|
if (!int.TryParse(pxStr, out var px)) continue;
|
|
if (px is <= 0 or > 500) continue;
|
|
if (bars.Contains(px)) continue;
|
|
bars.Add(px);
|
|
}
|
|
}
|
|
|
|
return bars.OrderBy(pixels => pixels).ToArray();
|
|
}
|
|
}
|
|
|
|
public int GetBarsLength(int[] bars)
|
|
{
|
|
if (bars.Length == 0) return 0;
|
|
int len = (int) (bars.Sum() + (bars.Length + 1) * _barSpacing * Yppmm);
|
|
if (_barFenceThickness > 0)
|
|
{
|
|
len = Math.Max(len, len + _barFenceThickness * 2 + _barFenceOffset * 2);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
public bool TextEnabled
|
|
{
|
|
get => _textEnabled;
|
|
set => RaiseAndSetIfChanged(ref _textEnabled, value);
|
|
}
|
|
|
|
public static Array TextFonts => Enum.GetValues(typeof(FontFace));
|
|
|
|
public FontFace TextFont
|
|
{
|
|
get => _textFont;
|
|
set => RaiseAndSetIfChanged(ref _textFont, value);
|
|
}
|
|
|
|
public double TextScale
|
|
{
|
|
get => _textScale;
|
|
set => RaiseAndSetIfChanged(ref _textScale, Math.Round(value, 2));
|
|
}
|
|
|
|
public byte TextThickness
|
|
{
|
|
get => _textThickness;
|
|
set => RaiseAndSetIfChanged(ref _textThickness, value);
|
|
}
|
|
|
|
public string Text
|
|
{
|
|
get => _text;
|
|
set => RaiseAndSetIfChanged(ref _text, value);
|
|
}
|
|
|
|
public Size TextSize
|
|
{
|
|
get
|
|
{
|
|
if (!_textEnabled || string.IsNullOrWhiteSpace(_text)) return Size.Empty;
|
|
int baseline = 0;
|
|
return CvInvoke.GetTextSize(_text, _textFont, _textScale, _textThickness, ref baseline);
|
|
}
|
|
}
|
|
|
|
public bool MultipleBrightness
|
|
{
|
|
get => _multipleBrightness;
|
|
set => RaiseAndSetIfChanged(ref _multipleBrightness, value);
|
|
}
|
|
|
|
public CalibrateExposureFinderMultipleBrightnessExcludeFrom MultipleBrightnessExcludeFrom
|
|
{
|
|
get => _multipleBrightnessExcludeFrom;
|
|
set => RaiseAndSetIfChanged(ref _multipleBrightnessExcludeFrom, value);
|
|
}
|
|
|
|
public string MultipleBrightnessValues
|
|
{
|
|
get => _multipleBrightnessValues;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleBrightnessValues, value)) return;
|
|
RaisePropertyChanged(nameof(MultipleBrightnessTable));
|
|
}
|
|
}
|
|
|
|
public List<ExposureItem> MultipleBrightnessTable
|
|
{
|
|
get
|
|
{
|
|
var brightnesses = MultipleBrightnessValuesArray;
|
|
return brightnesses.Select(brightness => (ExposureItem)
|
|
new(
|
|
_layerHeight,
|
|
Math.Round(brightness * _bottomExposure / byte.MaxValue, 2),
|
|
Math.Round(brightness * _normalExposure / byte.MaxValue, 2),
|
|
brightness)).ToList();
|
|
}
|
|
}
|
|
|
|
public decimal MultipleBrightnessGenExposureTime
|
|
{
|
|
get => _multipleBrightnessGenExposureTime;
|
|
set => RaiseAndSetIfChanged(ref _multipleBrightnessGenExposureTime, value);
|
|
}
|
|
|
|
public byte MaximumAntiAliasing => FileFormat.MaximumAntiAliasing;
|
|
|
|
public byte MultipleBrightnessGenEmulatedAALevel
|
|
{
|
|
get => _multipleBrightnessGenEmulatedAALevel;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleBrightnessGenEmulatedAALevel, value)) return;
|
|
GenerateBrightnessExposureFractions();
|
|
}
|
|
}
|
|
|
|
public byte MultipleBrightnessGenExposureFractions
|
|
{
|
|
get => _multipleBrightnessGenExposureFractions;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleBrightnessGenExposureFractions, value)) return;
|
|
GenerateBrightnessExposureFractions();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all holes in pixels and ordered
|
|
/// </summary>
|
|
public byte[] MultipleBrightnessValuesArray
|
|
{
|
|
get
|
|
{
|
|
List<byte> values = new();
|
|
if (!string.IsNullOrWhiteSpace(_multipleBrightnessValues))
|
|
{
|
|
var split = _multipleBrightnessValues.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var brightnessStr in split)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(brightnessStr)) continue;
|
|
if (!byte.TryParse(brightnessStr, out var brightness)) continue;
|
|
if (brightness is <= 0 or > 255) continue;
|
|
if (values.Contains(brightness)) continue;
|
|
values.Add(brightness);
|
|
}
|
|
}
|
|
|
|
return values.OrderByDescending(brightness => brightness).ToArray();
|
|
}
|
|
}
|
|
|
|
|
|
public bool MultipleLayerHeight
|
|
{
|
|
get => _multipleLayerHeight;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleLayerHeight, value)) return;
|
|
RaisePropertyChanged(nameof(AvailableLayerHeights));
|
|
}
|
|
}
|
|
|
|
public decimal MultipleLayerHeightMaximum
|
|
{
|
|
get => _multipleLayerHeightMaximum;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleLayerHeightMaximum, value)) return;
|
|
RaisePropertyChanged(nameof(AvailableLayerHeights));
|
|
}
|
|
}
|
|
|
|
public decimal MultipleLayerHeightStep
|
|
{
|
|
get => _multipleLayerHeightStep;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleLayerHeightStep, value)) return;
|
|
RaisePropertyChanged(nameof(AvailableLayerHeights));
|
|
}
|
|
}
|
|
|
|
public CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes MultipleExposuresBaseLayersPrintMode
|
|
{
|
|
get => _multipleExposuresBaseLayersPrintMode;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _multipleExposuresBaseLayersPrintMode, value)) return;
|
|
RaisePropertyChanged(nameof(IsMultipleExposuresBaseLayersPrintModeCustom));
|
|
}
|
|
}
|
|
|
|
public bool IsMultipleExposuresBaseLayersPrintModeCustom => _multipleExposuresBaseLayersPrintMode == CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes.Custom;
|
|
|
|
public decimal MultipleExposuresBaseLayersCustomExposure
|
|
{
|
|
get => _multipleExposuresBaseLayersCustomExposure;
|
|
set => RaiseAndSetIfChanged(ref _multipleExposuresBaseLayersCustomExposure, value);
|
|
}
|
|
|
|
public bool DifferentSettingsForSamePositionedLayers
|
|
{
|
|
get => _differentSettingsForSamePositionedLayers;
|
|
set => RaiseAndSetIfChanged(ref _differentSettingsForSamePositionedLayers, value);
|
|
}
|
|
|
|
public bool SamePositionedLayersLiftHeightEnabled
|
|
{
|
|
get => _samePositionedLayersLiftHeightEnabled;
|
|
set => RaiseAndSetIfChanged(ref _samePositionedLayersLiftHeightEnabled, value);
|
|
}
|
|
|
|
public decimal SamePositionedLayersLiftHeight
|
|
{
|
|
get => _samePositionedLayersLiftHeight;
|
|
set => RaiseAndSetIfChanged(ref _samePositionedLayersLiftHeight, Math.Round(value, 2));
|
|
}
|
|
|
|
public bool SamePositionedLayersWaitTimeBeforeCureEnabled
|
|
{
|
|
get => _samePositionedLayersWaitTimeBeforeCureEnabled;
|
|
set => RaiseAndSetIfChanged(ref _samePositionedLayersWaitTimeBeforeCureEnabled, value);
|
|
}
|
|
|
|
public decimal SamePositionedLayersWaitTimeBeforeCure
|
|
{
|
|
get => _samePositionedLayersWaitTimeBeforeCure;
|
|
set => RaiseAndSetIfChanged(ref _samePositionedLayersWaitTimeBeforeCure, Math.Round(value, 2));
|
|
}
|
|
|
|
public bool MultipleExposures
|
|
{
|
|
get => _multipleExposures;
|
|
set => RaiseAndSetIfChanged(ref _multipleExposures, value);
|
|
}
|
|
|
|
public CalibrateExposureFinderExposureGenTypes ExposureGenType
|
|
{
|
|
get => _exposureGenType;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenType, value);
|
|
}
|
|
|
|
public bool ExposureGenIgnoreBaseExposure
|
|
{
|
|
get => _exposureGenIgnoreBaseExposure;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenIgnoreBaseExposure, value);
|
|
}
|
|
|
|
public decimal ExposureGenBottomStep
|
|
{
|
|
get => _exposureGenBottomStep;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenBottomStep, Math.Round(value, 2));
|
|
}
|
|
|
|
public decimal ExposureGenNormalStep
|
|
{
|
|
get => _exposureGenNormalStep;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenNormalStep, Math.Round(value, 2));
|
|
}
|
|
|
|
public byte ExposureGenTests
|
|
{
|
|
get => _exposureGenTests;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenTests, value);
|
|
}
|
|
|
|
public decimal ExposureGenManualLayerHeight
|
|
{
|
|
get => _exposureGenManualLayerHeight;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenManualLayerHeight, value);
|
|
}
|
|
|
|
public decimal[] AvailableLayerHeights
|
|
{
|
|
get
|
|
{
|
|
List<decimal> layerHeights = new();
|
|
var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight;
|
|
for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep)
|
|
{
|
|
layerHeights.Add(Layer.RoundHeight(layerHeight));
|
|
}
|
|
|
|
return layerHeights.ToArray();
|
|
}
|
|
}
|
|
|
|
public decimal ExposureGenManualBottom
|
|
{
|
|
get => _exposureGenManualBottom;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenManualBottom, value);
|
|
}
|
|
|
|
public decimal ExposureGenManualNormal
|
|
{
|
|
get => _exposureGenManualNormal;
|
|
set => RaiseAndSetIfChanged(ref _exposureGenManualNormal, value);
|
|
}
|
|
|
|
public ExposureItem ExposureManualEntry => new (_exposureGenManualLayerHeight, _exposureGenManualBottom, _exposureGenManualNormal);
|
|
|
|
|
|
public RangeObservableCollection<ExposureItem> ExposureTable
|
|
{
|
|
get => _exposureTable;
|
|
set => RaiseAndSetIfChanged(ref _exposureTable, value);
|
|
}
|
|
|
|
public bool BullsEyeEnabled
|
|
{
|
|
get => _bullsEyeEnabled;
|
|
set => RaiseAndSetIfChanged(ref _bullsEyeEnabled, value);
|
|
}
|
|
|
|
public string BullsEyeConfigurationPx
|
|
{
|
|
get => _bullsEyeConfigurationPx;
|
|
set => RaiseAndSetIfChanged(ref _bullsEyeConfigurationPx, value);
|
|
}
|
|
|
|
public string BullsEyeConfigurationMm
|
|
{
|
|
get => _bullsEyeConfigurationMm;
|
|
set => RaiseAndSetIfChanged(ref _bullsEyeConfigurationMm, value);
|
|
}
|
|
|
|
public byte BullsEyeFenceThickness
|
|
{
|
|
get => _bullsEyeFenceThickness;
|
|
set => RaiseAndSetIfChanged(ref _bullsEyeFenceThickness, value);
|
|
}
|
|
|
|
public sbyte BullsEyeFenceOffset
|
|
{
|
|
get => _bullsEyeFenceOffset;
|
|
set => RaiseAndSetIfChanged(ref _bullsEyeFenceOffset, value);
|
|
}
|
|
|
|
public bool BullsEyeInvertQuadrants
|
|
{
|
|
get => _bullsEyeInvertQuadrants;
|
|
set => RaiseAndSetIfChanged(ref _bullsEyeInvertQuadrants, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all holes in pixels and ordered
|
|
/// </summary>
|
|
public BullsEyeCircle[] BullsEyes
|
|
{
|
|
get
|
|
{
|
|
if (!_bullsEyeEnabled)
|
|
{
|
|
return Array.Empty<BullsEyeCircle>();
|
|
}
|
|
|
|
List<BullsEyeCircle> bulleyes = new();
|
|
|
|
if (_unitOfMeasure == CalibrateExposureFinderMeasures.Millimeters)
|
|
{
|
|
var splitGroup = _bullsEyeConfigurationMm.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var group in splitGroup)
|
|
{
|
|
var splitDiameterThickness = group.Split(':', StringSplitOptions.TrimEntries);
|
|
if (splitDiameterThickness.Length < 2) continue;
|
|
|
|
if (string.IsNullOrWhiteSpace(splitDiameterThickness[0]) ||
|
|
string.IsNullOrWhiteSpace(splitDiameterThickness[1])) continue;
|
|
if (!decimal.TryParse(splitDiameterThickness[0], out var diameterMm)) continue;
|
|
if (!decimal.TryParse(splitDiameterThickness[1], out var thicknessMm)) continue;
|
|
var diameter = (int)(diameterMm * Ppmm);
|
|
if (diameterMm is <= 0 or > 500) continue;
|
|
var thickness = (int)(thicknessMm * Ppmm);
|
|
if (thickness is <= 0 or > 500) continue;
|
|
if (bulleyes.Exists(circle => circle.Diameter == diameter)) continue;
|
|
bulleyes.Add(new BullsEyeCircle((ushort)diameter, (ushort)thickness));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var splitGroup = _bullsEyeConfigurationPx.Split(',', StringSplitOptions.TrimEntries);
|
|
foreach (var group in splitGroup)
|
|
{
|
|
var splitDiameterThickness = group.Split(':', StringSplitOptions.TrimEntries);
|
|
if (splitDiameterThickness.Length < 2) continue;
|
|
|
|
if (string.IsNullOrWhiteSpace(splitDiameterThickness[0]) ||
|
|
string.IsNullOrWhiteSpace(splitDiameterThickness[1])) continue;
|
|
if (!int.TryParse(splitDiameterThickness[0], out var diameter)) continue;
|
|
if (!int.TryParse(splitDiameterThickness[1], out var thickness)) continue;
|
|
if (diameter is <= 0 or > 500) continue;
|
|
if (thickness is <= 0 or > 500) continue;
|
|
if (bulleyes.Exists(circle => circle.Diameter == diameter)) continue;
|
|
bulleyes.Add(new BullsEyeCircle((ushort) diameter, (ushort) thickness));
|
|
}
|
|
}
|
|
|
|
return bulleyes.OrderBy(circle => circle.Diameter).DistinctBy(circle => circle.Diameter).ToArray();
|
|
}
|
|
}
|
|
public int GetBullsEyeMaxPanelDiameter(BullsEyeCircle[] bullseyes)
|
|
{
|
|
if (!_bullsEyeEnabled || bullseyes.Length == 0) return 0;
|
|
var diameter = GetBullsEyeMaxDiameter(bullseyes);
|
|
return Math.Max(diameter, diameter + _bullsEyeFenceThickness + _bullsEyeFenceOffset * 2);
|
|
}
|
|
|
|
public int GetBullsEyeMaxDiameter(BullsEyeCircle[] bullseyes)
|
|
{
|
|
if (!_bullsEyeEnabled || bullseyes.Length == 0) return 0;
|
|
return bullseyes[^1].Diameter + bullseyes[^1].Thickness / 2;
|
|
}
|
|
|
|
public bool PatternModel
|
|
{
|
|
get => _patternModel;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _patternModel, value)) return;
|
|
if (_patternModel)
|
|
{
|
|
if(SlicerFile is not null) LayerHeight = (decimal) SlicerFile.LayerHeight;
|
|
MultipleLayerHeight = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool PatternModelGlueBottomLayers
|
|
{
|
|
get => _patternModelGlueBottomLayers;
|
|
set => RaiseAndSetIfChanged(ref _patternModelGlueBottomLayers, value);
|
|
}
|
|
|
|
public bool PatternModelTextEnabled
|
|
{
|
|
get => _patternModelTextEnabled;
|
|
set => RaiseAndSetIfChanged(ref _patternModelTextEnabled, value);
|
|
}
|
|
|
|
|
|
public bool CanPatternModel => SlicerFile.BoundingRectangle.Width * 2 + _leftRightMargin * 2 + _partMargin * Xppmm < SlicerFile.ResolutionX ||
|
|
SlicerFile.BoundingRectangle.Height * 2 + _topBottomMargin * 2 + _partMargin * Yppmm < SlicerFile.ResolutionY;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public OperationCalibrateExposureFinder() { }
|
|
|
|
public OperationCalibrateExposureFinder(FileFormat slicerFile) : base(slicerFile)
|
|
{
|
|
if (SlicerFile.SupportPerLayerSettings)
|
|
{
|
|
_differentSettingsForSamePositionedLayers = true;
|
|
if (SlicerFile.SupportsGCode)
|
|
{
|
|
_samePositionedLayersLiftHeight = 0;
|
|
_samePositionedLayersWaitTimeBeforeCure = 2;
|
|
}
|
|
else
|
|
{
|
|
_samePositionedLayersLiftHeight = 0.1m;
|
|
_samePositionedLayersWaitTimeBeforeCure = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public override void InitWithSlicerFile()
|
|
{
|
|
base.InitWithSlicerFile();
|
|
|
|
_mirrorOutput = SlicerFile.DisplayMirror != Enumerations.FlipDirection.None;
|
|
|
|
if (SlicerFile.DisplayWidth > 0)
|
|
DisplayWidth = (decimal)SlicerFile.DisplayWidth;
|
|
if (SlicerFile.DisplayHeight > 0)
|
|
DisplayHeight = (decimal)SlicerFile.DisplayHeight;
|
|
|
|
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;
|
|
|
|
if (_exposureGenManualBottom == 0)
|
|
_exposureGenManualBottom = (decimal) SlicerFile.BottomExposureTime;
|
|
if (_exposureGenManualNormal == 0)
|
|
_exposureGenManualNormal = (decimal)SlicerFile.ExposureTime;
|
|
if (_multipleBrightnessGenExposureTime == 0)
|
|
_multipleBrightnessGenExposureTime = (decimal)SlicerFile.ExposureTime;
|
|
|
|
if (_multipleExposuresBaseLayersCustomExposure <= 0) _multipleExposuresBaseLayersCustomExposure = (decimal)SlicerFile.ExposureTime;
|
|
|
|
if (!SlicerFile.CanUseLayerExposureTime)
|
|
{
|
|
_multipleLayerHeight = false;
|
|
_multipleExposures = false;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(_multipleBrightnessValues))
|
|
{
|
|
_multipleBrightnessValues =
|
|
SlicerFile.IsAntiAliasingEmulated
|
|
? "255, 239, 223, 207, 191, 175, 159, 143"
|
|
: "255, 242, 230, 217, 204, 191";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Equality
|
|
|
|
private bool Equals(OperationCalibrateExposureFinder other)
|
|
{
|
|
return _displayWidth == other._displayWidth && _displayHeight == other._displayHeight && _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _topBottomMargin == other._topBottomMargin && _leftRightMargin == other._leftRightMargin && _chamferLayers == other._chamferLayers && _erodeBottomIterations == other._erodeBottomIterations && _partMargin == other._partMargin && _enableAntiAliasing == other._enableAntiAliasing && _mirrorOutput == other._mirrorOutput && _baseHeight == other._baseHeight && _featuresHeight == other._featuresHeight && _featuresMargin == other._featuresMargin && _staircaseThicknessPx == other._staircaseThicknessPx && _staircaseThicknessMm == other._staircaseThicknessMm && _holesEnabled == other._holesEnabled && _holeShape == other._holeShape && _unitOfMeasure == other._unitOfMeasure && _holeDiametersPx == other._holeDiametersPx && _holeDiametersMm == other._holeDiametersMm && _barsEnabled == other._barsEnabled && _barSpacing == other._barSpacing && _barLength == other._barLength && _barVerticalSplitter == other._barVerticalSplitter && _barFenceThickness == other._barFenceThickness && _barFenceOffset == other._barFenceOffset && _barThicknessesPx == other._barThicknessesPx && _barThicknessesMm == other._barThicknessesMm && _textEnabled == other._textEnabled && _textFont == other._textFont && _textScale.Equals(other._textScale) && _textThickness == other._textThickness && _text == other._text && _multipleBrightness == other._multipleBrightness && _multipleBrightnessExcludeFrom == other._multipleBrightnessExcludeFrom && _multipleBrightnessValues == other._multipleBrightnessValues && _multipleBrightnessGenExposureTime == other._multipleBrightnessGenExposureTime && _multipleBrightnessGenEmulatedAALevel == other._multipleBrightnessGenEmulatedAALevel && _multipleBrightnessGenExposureFractions == other._multipleBrightnessGenExposureFractions && _multipleLayerHeight == other._multipleLayerHeight && _multipleLayerHeightMaximum == other._multipleLayerHeightMaximum && _multipleLayerHeightStep == other._multipleLayerHeightStep && _multipleExposuresBaseLayersPrintMode == other._multipleExposuresBaseLayersPrintMode && _multipleExposuresBaseLayersCustomExposure == other._multipleExposuresBaseLayersCustomExposure && _differentSettingsForSamePositionedLayers == other._differentSettingsForSamePositionedLayers && _samePositionedLayersLiftHeightEnabled == other._samePositionedLayersLiftHeightEnabled && _samePositionedLayersLiftHeight == other._samePositionedLayersLiftHeight && _samePositionedLayersWaitTimeBeforeCureEnabled == other._samePositionedLayersWaitTimeBeforeCureEnabled && _samePositionedLayersWaitTimeBeforeCure == other._samePositionedLayersWaitTimeBeforeCure && _multipleExposures == other._multipleExposures && _exposureGenType == other._exposureGenType && _exposureGenIgnoreBaseExposure == other._exposureGenIgnoreBaseExposure && _exposureGenBottomStep == other._exposureGenBottomStep && _exposureGenNormalStep == other._exposureGenNormalStep && _exposureGenTests == other._exposureGenTests && _exposureGenManualLayerHeight == other._exposureGenManualLayerHeight && _exposureGenManualBottom == other._exposureGenManualBottom && _exposureGenManualNormal == other._exposureGenManualNormal && Equals(_exposureTable, other._exposureTable) && _bullsEyeEnabled == other._bullsEyeEnabled && _bullsEyeConfigurationPx == other._bullsEyeConfigurationPx && _bullsEyeConfigurationMm == other._bullsEyeConfigurationMm && _bullsEyeInvertQuadrants == other._bullsEyeInvertQuadrants && _counterTrianglesEnabled == other._counterTrianglesEnabled && _counterTrianglesTipOffset == other._counterTrianglesTipOffset && _counterTrianglesFence == other._counterTrianglesFence && _patternModel == other._patternModel && _bullsEyeFenceThickness == other._bullsEyeFenceThickness && _bullsEyeFenceOffset == other._bullsEyeFenceOffset && _patternModelGlueBottomLayers == other._patternModelGlueBottomLayers && _patternModelTextEnabled == other._patternModelTextEnabled;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return ReferenceEquals(this, obj) || obj is OperationCalibrateExposureFinder other && Equals(other);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hashCode = new HashCode();
|
|
hashCode.Add(_displayWidth);
|
|
hashCode.Add(_displayHeight);
|
|
hashCode.Add(_layerHeight);
|
|
hashCode.Add(_bottomLayers);
|
|
hashCode.Add(_bottomExposure);
|
|
hashCode.Add(_normalExposure);
|
|
hashCode.Add(_topBottomMargin);
|
|
hashCode.Add(_leftRightMargin);
|
|
hashCode.Add(_chamferLayers);
|
|
hashCode.Add(_erodeBottomIterations);
|
|
hashCode.Add(_partMargin);
|
|
hashCode.Add(_enableAntiAliasing);
|
|
hashCode.Add(_mirrorOutput);
|
|
hashCode.Add(_baseHeight);
|
|
hashCode.Add(_featuresHeight);
|
|
hashCode.Add(_featuresMargin);
|
|
hashCode.Add(_staircaseThicknessPx);
|
|
hashCode.Add(_staircaseThicknessMm);
|
|
hashCode.Add(_holesEnabled);
|
|
hashCode.Add((int)_holeShape);
|
|
hashCode.Add((int)_unitOfMeasure);
|
|
hashCode.Add(_holeDiametersPx);
|
|
hashCode.Add(_holeDiametersMm);
|
|
hashCode.Add(_barsEnabled);
|
|
hashCode.Add(_barSpacing);
|
|
hashCode.Add(_barLength);
|
|
hashCode.Add(_barVerticalSplitter);
|
|
hashCode.Add(_barFenceThickness);
|
|
hashCode.Add(_barFenceOffset);
|
|
hashCode.Add(_barThicknessesPx);
|
|
hashCode.Add(_barThicknessesMm);
|
|
hashCode.Add(_textEnabled);
|
|
hashCode.Add((int)_textFont);
|
|
hashCode.Add(_textScale);
|
|
hashCode.Add(_textThickness);
|
|
hashCode.Add(_text);
|
|
hashCode.Add(_multipleBrightness);
|
|
hashCode.Add((int)_multipleBrightnessExcludeFrom);
|
|
hashCode.Add(_multipleBrightnessValues);
|
|
hashCode.Add(_multipleBrightnessGenExposureTime);
|
|
hashCode.Add(_multipleBrightnessGenEmulatedAALevel);
|
|
hashCode.Add(_multipleBrightnessGenExposureFractions);
|
|
hashCode.Add(_multipleLayerHeight);
|
|
hashCode.Add(_multipleLayerHeightMaximum);
|
|
hashCode.Add(_multipleLayerHeightStep);
|
|
hashCode.Add((int)_multipleExposuresBaseLayersPrintMode);
|
|
hashCode.Add(_multipleExposuresBaseLayersCustomExposure);
|
|
hashCode.Add(_differentSettingsForSamePositionedLayers);
|
|
hashCode.Add(_samePositionedLayersLiftHeightEnabled);
|
|
hashCode.Add(_samePositionedLayersLiftHeight);
|
|
hashCode.Add(_samePositionedLayersWaitTimeBeforeCureEnabled);
|
|
hashCode.Add(_samePositionedLayersWaitTimeBeforeCure);
|
|
hashCode.Add(_multipleExposures);
|
|
hashCode.Add((int)_exposureGenType);
|
|
hashCode.Add(_exposureGenIgnoreBaseExposure);
|
|
hashCode.Add(_exposureGenBottomStep);
|
|
hashCode.Add(_exposureGenNormalStep);
|
|
hashCode.Add(_exposureGenTests);
|
|
hashCode.Add(_exposureGenManualLayerHeight);
|
|
hashCode.Add(_exposureGenManualBottom);
|
|
hashCode.Add(_exposureGenManualNormal);
|
|
hashCode.Add(_exposureTable);
|
|
hashCode.Add(_bullsEyeEnabled);
|
|
hashCode.Add(_bullsEyeConfigurationPx);
|
|
hashCode.Add(_bullsEyeConfigurationMm);
|
|
hashCode.Add(_bullsEyeInvertQuadrants);
|
|
hashCode.Add(_counterTrianglesEnabled);
|
|
hashCode.Add(_counterTrianglesTipOffset);
|
|
hashCode.Add(_counterTrianglesFence);
|
|
hashCode.Add(_patternModel);
|
|
hashCode.Add(_bullsEyeFenceThickness);
|
|
hashCode.Add(_bullsEyeFenceOffset);
|
|
hashCode.Add(_patternModelGlueBottomLayers);
|
|
hashCode.Add(_patternModelTextEnabled);
|
|
return hashCode.ToHashCode();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
public void SortExposureTable()
|
|
{
|
|
_exposureTable.Sort();
|
|
}
|
|
|
|
public void SanitizeExposureTable()
|
|
{
|
|
_exposureTable.ReplaceCollection(GetSanitizedExposureTable());
|
|
}
|
|
|
|
public List<ExposureItem> GetSanitizedExposureTable()
|
|
{
|
|
var list = _exposureTable.ToList().Distinct().ToList();
|
|
list.Sort();
|
|
return list;
|
|
}
|
|
|
|
public void GenerateBrightnessExposureFractions()
|
|
{
|
|
var fractions = _multipleBrightnessGenExposureFractions;
|
|
var increment = 255f / _multipleBrightnessGenEmulatedAALevel;
|
|
|
|
byte[] validAA = new byte[fractions];
|
|
|
|
for (byte frac = 0; frac < fractions; frac++)
|
|
{
|
|
validAA[frac] = (byte)(byte.MaxValue - increment * frac);
|
|
}
|
|
|
|
MultipleBrightnessValues = string.Join(", ", validAA);
|
|
}
|
|
|
|
public void GenerateExposureTable()
|
|
{
|
|
var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight;
|
|
List<ExposureItem> list = new();
|
|
for (decimal layerHeight = _layerHeight;
|
|
layerHeight <= endLayerHeight;
|
|
layerHeight += _multipleLayerHeightStep)
|
|
{
|
|
if(!_exposureGenIgnoreBaseExposure)
|
|
list.Add(new ExposureItem(layerHeight, _bottomExposure, _normalExposure));
|
|
for (ushort testN = 1; testN <= _exposureGenTests; testN++)
|
|
{
|
|
decimal bottomExposureTime = 0;
|
|
decimal exposureTime = 0;
|
|
|
|
switch (_exposureGenType)
|
|
{
|
|
case CalibrateExposureFinderExposureGenTypes.Linear:
|
|
bottomExposureTime = _bottomExposure + _exposureGenBottomStep * testN;
|
|
exposureTime = _normalExposure + _exposureGenNormalStep * testN;
|
|
break;
|
|
case CalibrateExposureFinderExposureGenTypes.Multiplier:
|
|
bottomExposureTime = _bottomExposure + _bottomExposure * layerHeight * _exposureGenBottomStep * testN;
|
|
exposureTime = _normalExposure + _normalExposure * layerHeight * _exposureGenNormalStep * testN;
|
|
break;
|
|
}
|
|
|
|
ExposureItem item = new(layerHeight, bottomExposureTime, exposureTime);
|
|
if(list.Contains(item)) continue; // Already on list, skip
|
|
list.Add(item);
|
|
}
|
|
}
|
|
|
|
ExposureTable = new(list);
|
|
}
|
|
|
|
public Mat[] GetLayers(bool isPreview = false)
|
|
{
|
|
var holes = Holes;
|
|
var bars = Bars;
|
|
var bulleyes = BullsEyes;
|
|
var textSize = TextSize;
|
|
|
|
int featuresMarginX = (int)(Xppmm * _featuresMargin);
|
|
int featuresMarginY = (int)(Yppmm * _featuresMargin);
|
|
ushort startCaseThickness = StaircaseThickness;
|
|
|
|
int holePanelWidth = holes.Length > 0 ? featuresMarginX * 2 + holes[^1] : 0;
|
|
int holePanelHeight = GetHolesHeight(holes);
|
|
int barsPanelHeight = GetBarsLength(bars);
|
|
int bulleyesDiameter = GetBullsEyeMaxDiameter(bulleyes);
|
|
int bulleyesPanelDiameter = GetBullsEyeMaxPanelDiameter(bulleyes);
|
|
int bulleyesRadius = bulleyesDiameter / 2;
|
|
int yLeftMaxSize = startCaseThickness + featuresMarginY + Math.Max(barsPanelHeight, textSize.Width) + bulleyesPanelDiameter;
|
|
int yRightMaxSize = startCaseThickness + holePanelHeight + featuresMarginY * 2;
|
|
|
|
int xSize = featuresMarginX;
|
|
int ySize = TextMarkingSpacing + featuresMarginY;
|
|
|
|
if (barsPanelHeight > 0 || textSize.Width > 0)
|
|
{
|
|
yLeftMaxSize += featuresMarginY;
|
|
}
|
|
|
|
int barLengthPx = (int) (_barLength * Xppmm);
|
|
int barSpacingPx = (int) (_barSpacing * Yppmm);
|
|
int barsPanelWidth = 0;
|
|
|
|
if (bars.Length > 0)
|
|
{
|
|
barsPanelWidth = barLengthPx * 2 + _barVerticalSplitter;
|
|
if (_barFenceThickness > 0)
|
|
{
|
|
barsPanelWidth = Math.Max(barsPanelWidth, barsPanelWidth + _barFenceThickness * 2 + _barFenceOffset * 2);
|
|
}
|
|
xSize += barsPanelWidth + featuresMarginX;
|
|
}
|
|
|
|
if (!textSize.IsEmpty)
|
|
{
|
|
xSize += textSize.Height + featuresMarginX;
|
|
}
|
|
|
|
int bullseyeYPos = yLeftMaxSize - bulleyesPanelDiameter / 2;
|
|
|
|
if (bulleyes.Length > 0)
|
|
{
|
|
xSize = Math.Max(xSize, bulleyesPanelDiameter + featuresMarginX * 2);
|
|
yLeftMaxSize += featuresMarginY + 24;
|
|
}
|
|
|
|
int bullseyeXPos = xSize / 2;
|
|
|
|
if (holePanelWidth > 0)
|
|
{
|
|
xSize -= featuresMarginX;
|
|
}
|
|
|
|
xSize += holePanelWidth;
|
|
int negativeSideWidth = xSize;
|
|
xSize += holePanelWidth;
|
|
|
|
int positiveSideWidth = xSize - holePanelWidth;
|
|
|
|
ySize += Math.Max(yLeftMaxSize, yRightMaxSize+10);
|
|
|
|
Rectangle rect = new(new Point(0, 0), new Size(xSize, ySize));
|
|
var layers = new Mat[2];
|
|
layers[0] = EmguExtensions.InitMat(rect.Size);
|
|
|
|
CvInvoke.Rectangle(layers[0], rect, EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
layers[1] = layers[0].NewBlank();
|
|
if (holes.Length > 0)
|
|
{
|
|
CvInvoke.Rectangle(layers[1],
|
|
new Rectangle(rect.Size.Width - holePanelWidth, 0, rect.Size.Width, layers[0].Height),
|
|
EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
|
|
|
|
int xPos = 0;
|
|
int yPos = 0;
|
|
|
|
// Print staircase
|
|
if (isPreview && startCaseThickness > 0)
|
|
{
|
|
CvInvoke.Rectangle(layers[1],
|
|
new Rectangle(0, 0, layers[1].Size.Width-holePanelWidth, startCaseThickness),
|
|
EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
|
|
// Print holes
|
|
for (var layerIndex = 0; layerIndex < layers.Length; layerIndex++)
|
|
{
|
|
var layer = layers[layerIndex];
|
|
yPos = featuresMarginY + startCaseThickness;
|
|
for (int i = 0; i < holes.Length; i++)
|
|
{
|
|
var diameter = holes[i];
|
|
var radius = diameter / 2;
|
|
xPos = layers[0].Width - holePanelWidth - featuresMarginX;
|
|
|
|
CalibrateExposureFinderShapes effectiveShape = _holeShape == CalibrateExposureFinderShapes.Square || diameter < 6 ?
|
|
CalibrateExposureFinderShapes.Square : CalibrateExposureFinderShapes.Circle;
|
|
|
|
switch (effectiveShape)
|
|
{
|
|
case CalibrateExposureFinderShapes.Square:
|
|
xPos -= diameter;
|
|
break;
|
|
case CalibrateExposureFinderShapes.Circle:
|
|
xPos -= radius;
|
|
yPos += radius;
|
|
break;
|
|
}
|
|
|
|
|
|
// Left side
|
|
if (layerIndex == 1)
|
|
{
|
|
if (diameter == 1)
|
|
{
|
|
layer.SetByte(xPos, yPos, 255);
|
|
}
|
|
else
|
|
{
|
|
switch (effectiveShape)
|
|
{
|
|
case CalibrateExposureFinderShapes.Square:
|
|
CvInvoke.Rectangle(layers[layerIndex],
|
|
new Rectangle(new Point(xPos, yPos), new Size(diameter-1, diameter-1)),
|
|
EmguExtensions.WhiteColor, -1,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
break;
|
|
case CalibrateExposureFinderShapes.Circle:
|
|
CvInvoke.Circle(layers[layerIndex],
|
|
new Point(xPos, yPos),
|
|
radius, EmguExtensions.WhiteColor, -1,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//holeXPos = layers[0].Width - holeXPos;
|
|
switch (effectiveShape)
|
|
{
|
|
case CalibrateExposureFinderShapes.Square:
|
|
xPos = layers[0].Width - rect.X - featuresMarginX - holes[^1];
|
|
break;
|
|
case CalibrateExposureFinderShapes.Circle:
|
|
xPos = layers[0].Width - rect.X - featuresMarginX - holes[^1] + radius;
|
|
break;
|
|
}
|
|
|
|
// Right side
|
|
if (diameter == 1)
|
|
{
|
|
layer.SetByte(xPos, yPos, 0);
|
|
}
|
|
else
|
|
{
|
|
switch (effectiveShape)
|
|
{
|
|
case CalibrateExposureFinderShapes.Square:
|
|
CvInvoke.Rectangle(layers[layerIndex],
|
|
new Rectangle(new Point(xPos, yPos), new Size(diameter-1, diameter-1)),
|
|
EmguExtensions.BlackColor, -1,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
break;
|
|
case CalibrateExposureFinderShapes.Circle:
|
|
CvInvoke.Circle(layers[layerIndex],
|
|
new Point(xPos, yPos),
|
|
radius, EmguExtensions.BlackColor, -1,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
yPos += featuresMarginY;
|
|
|
|
switch (effectiveShape)
|
|
{
|
|
case CalibrateExposureFinderShapes.Square:
|
|
yPos += diameter;
|
|
break;
|
|
case CalibrateExposureFinderShapes.Circle:
|
|
yPos += radius;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
xPos = featuresMarginX;
|
|
|
|
// Print Zebra bars
|
|
if (bars.Length > 0)
|
|
{
|
|
int yStartPos = startCaseThickness + featuresMarginY;
|
|
int xStartPos = xPos;
|
|
yPos = yStartPos + _barFenceThickness / 2 + _barFenceOffset;
|
|
xPos += _barFenceThickness / 2 + _barFenceOffset;
|
|
for (int i = 0; i < bars.Length; i++)
|
|
{
|
|
// Print positive bottom
|
|
CvInvoke.Rectangle(layers[1], new Rectangle(xPos, yPos, barLengthPx - 1, barSpacingPx - 1),
|
|
EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
// Print positive top
|
|
yPos += barSpacingPx;
|
|
CvInvoke.Rectangle(layers[1], new Rectangle(xPos + barLengthPx + _barVerticalSplitter, yPos, barLengthPx - 1, bars[i] - 1),
|
|
EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
yPos += bars[i];
|
|
}
|
|
|
|
// Left over
|
|
CvInvoke.Rectangle(layers[1], new Rectangle(xPos, yPos, barLengthPx - 1, barSpacingPx - 1),
|
|
EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
|
|
yPos += barSpacingPx;
|
|
|
|
if (_barFenceThickness > 0)
|
|
{
|
|
CvInvoke.Rectangle(layers[1], new Rectangle(
|
|
xStartPos - 1,
|
|
yStartPos - 1,
|
|
barsPanelWidth - _barFenceThickness + 1,
|
|
yPos - yStartPos + _barFenceThickness / 2 + _barFenceOffset + 1),
|
|
EmguExtensions.WhiteColor, _barFenceThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
|
|
yPos += _barFenceThickness * 2 + _barFenceOffset * 2;
|
|
}
|
|
|
|
xPos += featuresMarginX;
|
|
}
|
|
|
|
if (!textSize.IsEmpty)
|
|
{
|
|
CvInvoke.Rotate(layers[1], layers[1], RotateFlags.Rotate90CounterClockwise);
|
|
CvInvoke.PutText(layers[1], _text, new Point(startCaseThickness + featuresMarginY, layers[1].Height - barsPanelWidth - featuresMarginX * (barsPanelWidth > 0 ? 2 : 1)), _textFont, _textScale, EmguExtensions.WhiteColor, _textThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.Rotate(layers[1], layers[1], RotateFlags.Rotate90Clockwise);
|
|
}
|
|
|
|
// Print bullseye
|
|
if (bulleyes.Length > 0)
|
|
{
|
|
yPos = bullseyeYPos;
|
|
foreach (var circle in bulleyes)
|
|
{
|
|
CvInvoke.Circle(layers[1], new Point(bullseyeXPos, yPos), circle.Radius, EmguExtensions.WhiteColor, circle.Thickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
|
|
if (_bullsEyeInvertQuadrants)
|
|
{
|
|
var matRoi1 = new Mat(layers[1], new Rectangle(bullseyeXPos, yPos - bulleyesRadius - 5, bulleyesRadius + 6, bulleyesRadius + 5));
|
|
var matRoi2 = new Mat(layers[1], new Rectangle(bullseyeXPos - bulleyesRadius - 5, yPos, bulleyesRadius + 5, bulleyesRadius + 6));
|
|
//using var mask = matRoi1.CloneBlank();
|
|
|
|
//CvInvoke.Circle(mask, new Point(mask.Width / 2, mask.Height / 2), bulleyesRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
//CvInvoke.Circle(mask, new Point(mask.Width / 2, mask.Height / 2), BullsEyes[^1].Radius, EmguExtensions.WhiteByte, BullsEyes[^1].Thickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
|
|
CvInvoke.BitwiseNot(matRoi1, matRoi1);
|
|
CvInvoke.BitwiseNot(matRoi2, matRoi2);
|
|
}
|
|
|
|
if (_bullsEyeFenceThickness > 0)
|
|
{
|
|
CvInvoke.Rectangle(layers[1],
|
|
new Rectangle(
|
|
new Point(
|
|
bullseyeXPos - bulleyesRadius - 5 - _bullsEyeFenceOffset - _bullsEyeFenceThickness / 2,
|
|
yPos - bulleyesRadius - 5 - _bullsEyeFenceOffset - _bullsEyeFenceThickness / 2),
|
|
new Size(
|
|
bulleyesDiameter + 10 + _bullsEyeFenceOffset*2 + _bullsEyeFenceThickness,
|
|
bulleyesDiameter + 10 + _bullsEyeFenceOffset*2 + _bullsEyeFenceThickness)),
|
|
EmguExtensions.WhiteColor,
|
|
_bullsEyeFenceThickness,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
|
|
|
|
yPos += bulleyesRadius;
|
|
}
|
|
|
|
if (isPreview)
|
|
{
|
|
var textHeightStart = layers[1].Height - featuresMarginY - TextMarkingSpacing;
|
|
CvInvoke.PutText(layers[1], $"{Microns}u", new Point(TextMarkingStartX, textHeightStart), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(layers[1], $"{_bottomExposure}s", new Point(TextMarkingStartX, textHeightStart + TextMarkingLineBreak), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(layers[1], $"{_normalExposure}s", new Point(TextMarkingStartX, textHeightStart + TextMarkingLineBreak * 2), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
if (holes.Length > 0)
|
|
{
|
|
CvInvoke.PutText(layers[1], $"{Microns}u", new Point(layers[1].Width - featuresMarginX * 2 - holes[^1] + TextMarkingStartX, textHeightStart), TextMarkingFontFace, TextMarkingScale, EmguExtensions.BlackColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(layers[1], $"{_bottomExposure}s", new Point(layers[1].Width - featuresMarginX * 2 - holes[^1] + TextMarkingStartX, textHeightStart + TextMarkingLineBreak), TextMarkingFontFace, TextMarkingScale, EmguExtensions.BlackColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(layers[1], $"{_normalExposure}s", new Point(layers[1].Width - featuresMarginX * 2 - holes[^1] + TextMarkingStartX, textHeightStart + TextMarkingLineBreak * 2), TextMarkingFontFace, TextMarkingScale, EmguExtensions.BlackColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
}
|
|
|
|
if (negativeSideWidth >= 200 && _counterTrianglesEnabled)
|
|
{
|
|
xPos = 120;
|
|
int triangleHeight = TextMarkingSpacing + 19;
|
|
int triangleWidth = (negativeSideWidth - xPos - featuresMarginX) / 2;
|
|
int triangleWidthQuarter = triangleWidth / 4;
|
|
|
|
if (triangleWidth > 5)
|
|
{
|
|
yPos = layers[1].Height - featuresMarginY - triangleHeight + 1;
|
|
int yHalfPos = yPos + triangleHeight / 2;
|
|
int yPosEnd = layers[1].Height - featuresMarginY + 1;
|
|
|
|
var triangles = new Point[4][];
|
|
|
|
triangles[0] = new Point[] // Left
|
|
{
|
|
new(xPos, yPos), // Top Left
|
|
new(xPos + triangleWidth, yHalfPos), // Middle
|
|
new(xPos, yPosEnd), // Bottom Left
|
|
};
|
|
triangles[1] = new Point[] // Right
|
|
{
|
|
new(xPos + triangleWidth * 2, yPos), // Top Right
|
|
new(xPos + triangleWidth, yHalfPos), // Middle
|
|
new(xPos + triangleWidth * 2, yPosEnd), // Bottom Right
|
|
};
|
|
triangles[2] = new Point[] // Top
|
|
{
|
|
new(xPos + triangleWidth - triangleWidthQuarter, yPos), // Top Left
|
|
new(xPos + triangleWidth + triangleWidthQuarter, yPos), // Top Right
|
|
new(xPos + triangleWidth, yHalfPos - _counterTrianglesTipOffset), // Middle
|
|
};
|
|
triangles[3] = new Point[] // Bottom
|
|
{
|
|
new(xPos + triangleWidth - triangleWidthQuarter, yPosEnd), // Bottom Left
|
|
new(xPos + triangleWidth + triangleWidthQuarter, yPosEnd), // Bottom Right
|
|
new(xPos + triangleWidth, yHalfPos + _counterTrianglesTipOffset), // Middle
|
|
};
|
|
|
|
foreach (var triangle in triangles)
|
|
{
|
|
using var vec = new VectorOfPoint(triangle);
|
|
CvInvoke.FillPoly(layers[1], vec, EmguExtensions.WhiteColor,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
|
|
/*byte size = 60;
|
|
var matRoi = new Mat(layers[1], new Rectangle(
|
|
new Point(xPos + triangleWidth - size / 2, yHalfPos - size / 2),
|
|
new Size(size, size)));
|
|
|
|
CvInvoke.BitwiseNot(matRoi, matRoi);*/
|
|
|
|
|
|
if (_counterTrianglesFence)
|
|
{
|
|
byte outlineThickness = 8;
|
|
//byte outlineThicknessHalf = (byte)(outlineThickness / 2);
|
|
|
|
CvInvoke.Rectangle(layers[1], new Rectangle(
|
|
new Point(triangles[0][0].X - 0, triangles[0][0].Y - 0),
|
|
new Size(triangleWidth * 2 + 0, triangleHeight + 0)
|
|
), EmguExtensions.WhiteColor, outlineThickness,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
}
|
|
}
|
|
// Print a hardcoded spiral if have space
|
|
/*if (positiveSideWidth >= 250000)
|
|
{
|
|
var mat = layers[0].CloneBlank();
|
|
var matMask = layers[0].CloneBlank();
|
|
xPos = (int) ((layers[0].Width - holePanelWidth) / 1.8);
|
|
yPos = layers[0].Height - featuresMarginY - TextMarkingSpacing / 2;
|
|
byte circleThickness = 5;
|
|
byte radiusStep = 13;
|
|
int count = -1;
|
|
int maxRadius = 0;
|
|
//bool white = true;
|
|
|
|
for (int radius = radiusStep;radius <= 100; radius += (radiusStep + count))
|
|
{
|
|
count++;
|
|
CvInvoke.Circle(mat, new Point(xPos, yPos), radius, EmguExtensions.WhiteByte, circleThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
maxRadius = radius;
|
|
}
|
|
|
|
CvInvoke.Circle(mat, new Point(xPos, yPos), 5, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.Circle(matMask, new Point(xPos, yPos), maxRadius+2, EmguExtensions.WhiteByte, -1);
|
|
|
|
var matRoi1 = new Mat(mat, new Rectangle(xPos, yPos - maxRadius-1, maxRadius+2, maxRadius+1));
|
|
var matRoi2 = new Mat(mat, new Rectangle(xPos-maxRadius-1, yPos, maxRadius+1, Math.Min(mat.Height- yPos, maxRadius)));
|
|
|
|
CvInvoke.BitwiseNot(matRoi1, matRoi1);
|
|
CvInvoke.BitwiseNot(matRoi2, matRoi2);
|
|
|
|
CvInvoke.BitwiseAnd(layers[0], mat, layers[1], matMask);
|
|
|
|
Point anchor = new Point(-1, -1);
|
|
//CvInvoke.MorphologyEx(layers[1], layers[1], MorphOp.Open, CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3,3), anchor), anchor, 1, BorderType.Reflect101, default);
|
|
|
|
mat.Dispose();
|
|
matMask.Dispose();
|
|
}*/
|
|
|
|
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, "Exposure Time Cal.", new Point(xSpacing, ySpacing * 2 - 10), fontFace, fontScale, new MCvScalar(0, 255, 255), fontThickness);
|
|
|
|
|
|
string text = string.Empty;
|
|
|
|
if (_multipleLayerHeight)
|
|
{
|
|
text += $"{Microns}um-{(ushort)(_multipleLayerHeightMaximum *1000)}um/{(ushort)(_multipleLayerHeightStep *1000)}um\n";
|
|
}
|
|
else
|
|
{
|
|
text += $"Layer height: {Microns}um\n";
|
|
}
|
|
|
|
if (_multipleExposures)
|
|
{
|
|
text += $"{_exposureTable[0].Exposure}s-{_exposureTable[^1].Exposure}s/{_exposureGenNormalStep}s";
|
|
if (!_patternModel)
|
|
{
|
|
text += $"\nObjects: {_exposureTable.Count}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
text += $"{_bottomExposure}s/{_normalExposure}s";
|
|
}
|
|
|
|
if (_patternModel)
|
|
{
|
|
text += "\nPatterned Model";
|
|
}
|
|
|
|
|
|
thumbnail.PutTextExtended(text, new Point(xSpacing, ySpacing * 3 - 20), fontFace, 0.8, EmguExtensions.WhiteColor, 2, 10);
|
|
|
|
|
|
/*CvInvoke.PutText(thumbnail, $"{Microns}um @ {BottomExposure}s/{NormalExposure}s", new Point(xSpacing, ySpacing * 3), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
|
|
if (_patternModel)
|
|
{
|
|
CvInvoke.PutText(thumbnail, $"Patterned Model", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
|
|
}
|
|
else
|
|
{
|
|
CvInvoke.PutText(thumbnail, $"Features: {(_staircaseThickness > 0 ? 1 : 0) + Holes.Length + Bars.Length + BullsEyes.Length + (_counterTrianglesEnabled ? 1 : 0)}", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
|
|
}*/
|
|
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
int sideMarginPx = (int)(_leftRightMargin * Xppmm);
|
|
int topBottomMarginPx = (int)(_topBottomMargin * Yppmm);
|
|
int partMarginXPx = (int)(_partMargin * Xppmm);
|
|
int partMarginYPx = (int)(_partMargin * Yppmm);
|
|
|
|
var anchor = new Point(-1, -1);
|
|
var kernel = EmguExtensions.Kernel3x3Rectangle;
|
|
|
|
if (_patternModel)
|
|
{
|
|
ConcurrentBag<Layer> parallelLayers = new();
|
|
Dictionary<ExposureItem, Point> table = new();
|
|
var boundingRectangle = SlicerFile.BoundingRectangle;
|
|
int xHalf = boundingRectangle.Width / 2;
|
|
int yHalf = boundingRectangle.Height / 2;
|
|
|
|
var brightnesses = MultipleBrightnessValuesArray;
|
|
var multipleExposures = _exposureTable.Where(item => item.IsValid && item.LayerHeight == (decimal) SlicerFile.LayerHeight).ToArray();
|
|
if (brightnesses.Length == 0 || !_multipleBrightness) brightnesses = new[] { byte.MaxValue };
|
|
if (multipleExposures.Length == 0 || !_multipleExposures) multipleExposures = new[] { new ExposureItem((decimal)SlicerFile.LayerHeight, _bottomExposure, _normalExposure) };
|
|
|
|
int currentX = sideMarginPx;
|
|
int currentY = topBottomMarginPx;
|
|
Rectangle glueBottomLayerRectangle = new(new Point(currentX, currentY), Size.Empty);
|
|
foreach (var multipleExposure in multipleExposures)
|
|
{
|
|
foreach (var brightness in brightnesses)
|
|
{
|
|
if (currentX + boundingRectangle.Width + sideMarginPx >= SlicerFile.ResolutionX)
|
|
{
|
|
currentX = sideMarginPx;
|
|
currentY += boundingRectangle.Height + partMarginYPx;
|
|
}
|
|
|
|
if (currentY + boundingRectangle.Height + topBottomMarginPx >= SlicerFile.ResolutionY) break;
|
|
|
|
var item = multipleExposure.Clone();
|
|
item.Brightness = brightness;
|
|
table.Add(item, new Point(currentX, currentY));
|
|
|
|
glueBottomLayerRectangle.Size = new Size(currentX + boundingRectangle.Width, currentY + boundingRectangle.Height);
|
|
|
|
currentX += boundingRectangle.Width + partMarginXPx;
|
|
}
|
|
}
|
|
|
|
if (table.Count <= 1) return false;
|
|
ushort microns = SlicerFile.LayerHeightUm;
|
|
|
|
var tableGrouped = table.GroupBy(pair => new {pair.Key.LayerHeight, pair.Key.BottomExposure, pair.Key.Exposure}).Distinct();
|
|
SlicerFile.BottomLayerCount = _bottomLayers;
|
|
progress.ItemCount = (uint) (SlicerFile.LayerCount * table.Count);
|
|
Parallel.For(0, SlicerFile.LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
|
|
{
|
|
var layer = SlicerFile[layerIndex];
|
|
using var mat = layer.LayerMat;
|
|
var matRoi = new Mat(mat, boundingRectangle);
|
|
int layerCountOnHeight = (int)(layer.PositionZ / SlicerFile.LayerHeight);
|
|
foreach (var group in tableGrouped)
|
|
{
|
|
var newLayer = layer.Clone();
|
|
newLayer.ExposureTime = (float)(newLayer.IsBottomLayer ? group.Key.BottomExposure : group.Key.Exposure);
|
|
using var newMat = mat.NewBlank();
|
|
foreach (var brightness in brightnesses)
|
|
{
|
|
ExposureItem item = new(group.Key.LayerHeight, group.Key.BottomExposure, group.Key.Exposure, brightness);
|
|
if(!table.TryGetValue(item, out var point)) continue;
|
|
|
|
var newMatRoi = new Mat(newMat, new Rectangle(point, matRoi.Size));
|
|
matRoi.CopyTo(newMatRoi);
|
|
|
|
if (layer.IsBottomLayer)
|
|
{
|
|
if (_patternModelGlueBottomLayers)
|
|
{
|
|
newMatRoi.SetTo(EmguExtensions.WhiteColor);
|
|
}
|
|
}
|
|
|
|
if (layerCountOnHeight < _chamferLayers)
|
|
{
|
|
CvInvoke.Erode(newMatRoi, newMatRoi, kernel, anchor, _chamferLayers - layerCountOnHeight, BorderType.Reflect101, default);
|
|
}
|
|
|
|
if (layer.IsBottomLayer)
|
|
{
|
|
if (_erodeBottomIterations > 0)
|
|
{
|
|
CvInvoke.Erode(newMatRoi, newMatRoi, kernel, anchor, _erodeBottomIterations, BorderType.Reflect101, default);
|
|
}
|
|
|
|
if(_patternModelTextEnabled)
|
|
{
|
|
if (_multipleBrightness)
|
|
{
|
|
CvInvoke.PutText(newMatRoi, brightness.ToString(), new(xHalf - 60, yHalf + 20 - TextMarkingLineBreak * 4), TextMarkingFontFace, 2, EmguExtensions.BlackColor, 3, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
CvInvoke.PutText(newMatRoi, $"{microns}u", new(xHalf - 60, yHalf + 20 - TextMarkingLineBreak * 2), TextMarkingFontFace, 2, EmguExtensions.BlackColor, 3, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(newMatRoi, $"{group.Key.BottomExposure}s", new(xHalf - 60, yHalf + 20), TextMarkingFontFace, 2, EmguExtensions.BlackColor, 3, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(newMatRoi, $"{group.Key.Exposure}s", new(xHalf - 60, yHalf + 20 + TextMarkingLineBreak * 2), TextMarkingFontFace, 2, EmguExtensions.BlackColor, 3, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
}
|
|
|
|
if (brightness < 255)
|
|
{
|
|
if (_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.None ||
|
|
_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.Bottom && !layer.IsBottomLayer ||
|
|
_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.BottomAndBase && !layer.IsBottomLayer)
|
|
{
|
|
using var pattern = matRoi.New();
|
|
pattern.SetTo(new MCvScalar(byte.MaxValue - brightness));
|
|
CvInvoke.Subtract(newMatRoi, pattern, newMatRoi);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
newLayer.LayerMat = newMat;
|
|
parallelLayers.Add(newLayer);
|
|
progress.LockAndIncrement();
|
|
}
|
|
});
|
|
|
|
if (parallelLayers.IsEmpty) return false;
|
|
var layers = parallelLayers.OrderBy(layer => layer.PositionZ).ThenBy(layer => layer.ExposureTime).ToList();
|
|
|
|
progress.ResetNameAndProcessed("Optimized layers");
|
|
|
|
Layer currentLayer = layers[0];
|
|
for (var layerIndex = 1; layerIndex < layers.Count; layerIndex++)
|
|
{
|
|
progress.ThrowIfCancellationRequested();
|
|
progress++;
|
|
var layer = layers[layerIndex];
|
|
if (currentLayer.PositionZ != layer.PositionZ ||
|
|
currentLayer.ExposureTime != layer.ExposureTime) // Different layers, cache and continue
|
|
{
|
|
currentLayer = layer;
|
|
continue;
|
|
}
|
|
|
|
using var matCurrent = currentLayer.LayerMat;
|
|
using var mat = layer.LayerMat;
|
|
|
|
CvInvoke.Add(matCurrent, mat, matCurrent); // Sum layers
|
|
currentLayer.LayerMat = matCurrent;
|
|
|
|
layers[layerIndex] = null!; // Discard
|
|
}
|
|
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
layers.RemoveAll(layer => layer is null); // Discard equal layers
|
|
|
|
SlicerFile.SuppressRebuildPropertiesWork(() =>
|
|
{
|
|
SlicerFile.BottomExposureTime = (float)BottomExposure;
|
|
SlicerFile.ExposureTime = (float)NormalExposure;
|
|
SlicerFile.Layers = layers.ToArray();
|
|
});
|
|
}
|
|
else // No patterned
|
|
{
|
|
var layers = GetLayers();
|
|
progress.ItemCount = 0;
|
|
//SanitizeExposureTable();
|
|
if (layers[0].Width+sideMarginPx > SlicerFile.ResolutionX || layers[0].Height+topBottomMarginPx > SlicerFile.ResolutionY)
|
|
{
|
|
throw new InvalidOperationException("The used configuration can not produce a test due insufficient space.\n" +
|
|
"Try to adjust sides and/or top/bottom margins to gain space or object features to shorten the test size.");
|
|
//return false;
|
|
}
|
|
|
|
List<Layer> newLayers = new();
|
|
|
|
Dictionary<ExposureItem, Point> table = new();
|
|
var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight;
|
|
var totalHeight = TotalHeight;
|
|
uint layerIndex = 0;
|
|
int currentX = sideMarginPx;
|
|
int currentY = topBottomMarginPx;
|
|
int featuresMarginX = (int)(Xppmm * _featuresMargin);
|
|
int featuresMarginY = (int)(Yppmm * _featuresMargin);
|
|
ushort startCaseThickness = StaircaseThickness;
|
|
|
|
var holes = Holes;
|
|
int holePanelWidth = holes.Length > 0 ? featuresMarginX * 2 + holes[^1] : 0;
|
|
int staircaseWidth = layers[0].Width - holePanelWidth;
|
|
|
|
var brightnesses = MultipleBrightnessValuesArray;
|
|
if (brightnesses.Length == 0 || !_multipleBrightness) brightnesses = new[] { byte.MaxValue };
|
|
|
|
ExposureItem? lastExposureItem = null;
|
|
decimal lastcurrentHeight = 0;
|
|
|
|
void AddLayer(decimal currentHeight, decimal layerHeight, decimal bottomExposure, decimal normalExposure)
|
|
{
|
|
var layerDifference = currentHeight / layerHeight;
|
|
|
|
if (!layerDifference.IsInteger()) return; // Not at right height to process with layer height
|
|
//Debug.WriteLine($"{currentHeight} / {layerHeight} = {layerDifference}, Floor={Math.Floor(layerDifference)}");
|
|
|
|
int firstFeatureLayer = (int)(_baseHeight / layerHeight);
|
|
int lastLayer = (int)((_baseHeight + _featuresHeight) / layerHeight);
|
|
int layerCountOnHeight = (int)(currentHeight / layerHeight);
|
|
bool isBottomLayer = layerCountOnHeight <= _bottomLayers;
|
|
bool isBaseLayer = currentHeight <= _baseHeight;
|
|
ushort microns = (ushort)(layerHeight * 1000);
|
|
Point position;
|
|
bool addSomething = false;
|
|
|
|
bool reUseLastLayer =
|
|
lastExposureItem is not null &&
|
|
lastcurrentHeight == currentHeight &&
|
|
lastExposureItem.LayerHeight == layerHeight &&
|
|
(
|
|
(isBottomLayer && lastExposureItem.BottomExposure == bottomExposure || !isBottomLayer && lastExposureItem.Exposure == normalExposure) ||
|
|
(!isBottomLayer && isBaseLayer && _multipleExposuresBaseLayersPrintMode != CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes.Iterative)
|
|
);
|
|
|
|
using var mat = reUseLastLayer ? newLayers[^1].LayerMat : EmguExtensions.InitMat(SlicerFile.Resolution);
|
|
|
|
lastcurrentHeight = currentHeight;
|
|
|
|
foreach (var brightness in brightnesses)
|
|
{
|
|
var bottomExposureTemp = bottomExposure;
|
|
var normalExposureTemp = normalExposure;
|
|
ExposureItem key = new(layerHeight, bottomExposure, normalExposure, brightness);
|
|
lastExposureItem = key;
|
|
|
|
if (table.TryGetValue(key, out var pos))
|
|
{
|
|
position = pos;
|
|
}
|
|
else
|
|
{
|
|
if (currentX + layers[0].Width + sideMarginPx > SlicerFile.ResolutionX)
|
|
{
|
|
currentX = sideMarginPx;
|
|
currentY += layers[0].Height + partMarginYPx;
|
|
}
|
|
|
|
if (currentY + layers[0].Height + topBottomMarginPx > SlicerFile.ResolutionY)
|
|
{
|
|
break; // Reach the end
|
|
}
|
|
|
|
position = new Point(currentX, currentY);
|
|
table.Add(key, new Point(currentX, currentY));
|
|
|
|
currentX += layers[0].Width + partMarginXPx;
|
|
}
|
|
|
|
|
|
Mat matRoi = new(mat, new Rectangle(position, layers[0].Size));
|
|
|
|
layers[isBaseLayer ? 0 : 1].CopyTo(matRoi);
|
|
|
|
if (!isBaseLayer && startCaseThickness > 0)
|
|
{
|
|
int staircaseWidthIncrement = (int) Math.Ceiling(staircaseWidth / (_featuresHeight / layerHeight-1));
|
|
int staircaseLayer = layerCountOnHeight - firstFeatureLayer - 1;
|
|
int staircaseWidthForLayer = staircaseWidth - staircaseWidthIncrement * staircaseLayer;
|
|
if (staircaseWidthForLayer >= 0 && layerCountOnHeight != lastLayer)
|
|
{
|
|
CvInvoke.Rectangle(matRoi,
|
|
new Rectangle(staircaseWidth - staircaseWidthForLayer, 0, staircaseWidthForLayer, startCaseThickness),
|
|
EmguExtensions.WhiteColor, -1,
|
|
_enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
}
|
|
|
|
if (isBottomLayer && _erodeBottomIterations > 0)
|
|
{
|
|
CvInvoke.Erode(matRoi, matRoi, kernel, anchor, _erodeBottomIterations, BorderType.Reflect101, default);
|
|
}
|
|
|
|
if (layerCountOnHeight < _chamferLayers)
|
|
{
|
|
CvInvoke.Erode(matRoi, matRoi, kernel, anchor, _chamferLayers - layerCountOnHeight, BorderType.Reflect101, default);
|
|
}
|
|
|
|
if (_multipleBrightness && brightness < 255)
|
|
{
|
|
// normalExposure - 255
|
|
// x - brightness
|
|
normalExposureTemp = Math.Round(normalExposure * brightness / byte.MaxValue, 2);
|
|
if (_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.None)
|
|
{
|
|
bottomExposureTemp = Math.Round(bottomExposure * brightness / byte.MaxValue, 2);
|
|
}
|
|
}
|
|
|
|
var textHeightStart = matRoi.Height - featuresMarginY - TextMarkingSpacing;
|
|
CvInvoke.PutText(matRoi, $"{microns}u", new Point(TextMarkingStartX, textHeightStart), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(matRoi, $"{bottomExposureTemp}s", new Point(TextMarkingStartX, textHeightStart + TextMarkingLineBreak), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(matRoi, $"{normalExposureTemp}s", new Point(TextMarkingStartX, textHeightStart + TextMarkingLineBreak * 2), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
if (holes.Length > 0)
|
|
{
|
|
CvInvoke.PutText(matRoi, $"{microns}u", new Point(matRoi.Width - featuresMarginX * 2 - holes[^1] + TextMarkingStartX, textHeightStart), TextMarkingFontFace, TextMarkingScale, EmguExtensions.BlackColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(matRoi, $"{bottomExposureTemp}s", new Point(matRoi.Width - featuresMarginX * 2 - holes[^1] + TextMarkingStartX, textHeightStart + TextMarkingLineBreak), TextMarkingFontFace, TextMarkingScale, EmguExtensions.BlackColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
CvInvoke.PutText(matRoi, $"{normalExposureTemp}s", new Point(matRoi.Width - featuresMarginX * 2 - holes[^1] + TextMarkingStartX, textHeightStart + TextMarkingLineBreak * 2), TextMarkingFontFace, TextMarkingScale, EmguExtensions.BlackColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
}
|
|
|
|
if (_multipleBrightness)
|
|
{
|
|
CvInvoke.PutText(matRoi, brightness.ToString(), new Point(matRoi.Width / 3, 35), TextMarkingFontFace, TextMarkingScale, EmguExtensions.WhiteColor, TextMarkingThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
|
if (brightness < 255 &&
|
|
(_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.None ||
|
|
_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.Bottom && !isBottomLayer ||
|
|
_multipleBrightnessExcludeFrom == CalibrateExposureFinderMultipleBrightnessExcludeFrom.BottomAndBase && !isBottomLayer && !isBaseLayer)
|
|
)
|
|
{
|
|
using var pattern = matRoi.New();
|
|
//pattern.SetTo(new MCvScalar(brightness)); OLD
|
|
//CvInvoke.BitwiseAnd(matRoi, pattern, matRoi, matRoi); OLD
|
|
|
|
pattern.SetTo(new MCvScalar(byte.MaxValue - brightness));
|
|
CvInvoke.Subtract(matRoi, pattern, matRoi);
|
|
}
|
|
}
|
|
|
|
addSomething = true;
|
|
}
|
|
|
|
if (!addSomething) return;
|
|
|
|
if (reUseLastLayer)
|
|
{
|
|
var layer = newLayers[^1];
|
|
layer.LayerMat = mat;
|
|
|
|
if (!isBottomLayer && isBaseLayer)
|
|
{
|
|
layer.ExposureTime = _multipleExposuresBaseLayersPrintMode switch
|
|
{
|
|
CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes.UseLowest => (float) _exposureTable[0].Exposure,
|
|
CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes.UseMiddle => (float) _exposureTable[(int) Math.Ceiling((_exposureTable.Count - 1) / 2.0)].Exposure,
|
|
CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes.UseHighest => (float) _exposureTable[^1].Exposure,
|
|
CalibrateExposureFinderMultipleExposuresBaseLayersPrintModes.Custom => (float) _multipleExposuresBaseLayersCustomExposure,
|
|
_ => throw new ArgumentOutOfRangeException($"Unhandled type for {_multipleExposuresBaseLayersCustomExposure}")
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var layer = new Layer(layerIndex++, mat, SlicerFile)
|
|
{
|
|
PositionZ = (float)currentHeight,
|
|
ExposureTime = isBottomLayer ? (float)bottomExposure : (float)normalExposure,
|
|
IsModified = true
|
|
};
|
|
newLayers.Add(layer);
|
|
}
|
|
|
|
|
|
progress++;
|
|
}
|
|
|
|
for (decimal currentHeight = _layerHeight; currentHeight <= totalHeight; currentHeight += Layer.HeightPrecisionIncrement)
|
|
{
|
|
currentHeight = Layer.RoundHeight(currentHeight);
|
|
for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep)
|
|
{
|
|
progress.ThrowIfCancellationRequested();
|
|
layerHeight = Layer.RoundHeight(layerHeight);
|
|
|
|
if (_multipleExposures)
|
|
{
|
|
foreach (var exposureItem in _exposureTable)
|
|
{
|
|
if (exposureItem.IsValid && exposureItem.LayerHeight == layerHeight)
|
|
{
|
|
AddLayer(currentHeight, layerHeight, exposureItem.BottomExposure, exposureItem.Exposure);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddLayer(currentHeight, layerHeight, _bottomExposure, _normalExposure);
|
|
}
|
|
}
|
|
}
|
|
|
|
SlicerFile.SuppressRebuildPropertiesWork(() =>
|
|
{
|
|
SlicerFile.LayerHeight = (float)LayerHeight;
|
|
SlicerFile.BottomExposureTime = (float)BottomExposure;
|
|
SlicerFile.ExposureTime = (float)NormalExposure;
|
|
SlicerFile.BottomLayerCount = BottomLayers;
|
|
SlicerFile.TransitionLayerCount = 0;
|
|
SlicerFile.Layers = newLayers.ToArray();
|
|
});
|
|
|
|
if (_mirrorOutput)
|
|
{
|
|
var flip = SlicerFile.DisplayMirror;
|
|
if (flip == Enumerations.FlipDirection.None) flip = Enumerations.FlipDirection.Horizontally;
|
|
new OperationFlip(SlicerFile) { FlipDirection = Enumerations.ToOpenCVFlipType(flip) }.Execute(progress);
|
|
}
|
|
}
|
|
|
|
if (_multipleBrightness && SlicerFile.IsAntiAliasingEmulated)
|
|
{
|
|
/*SlicerFile.AntiAliasing = MultipleBrightnessValuesArray.Length switch
|
|
{
|
|
<= 2 => 2,
|
|
<= 4 => 4,
|
|
<= 8 => 8,
|
|
<= 16 => 16,
|
|
_ => 16
|
|
};*/
|
|
SlicerFile.AntiAliasing = _multipleBrightnessGenEmulatedAALevel;
|
|
}
|
|
|
|
if (SlicerFile.ThumbnailsCount > 0)
|
|
SlicerFile.SetThumbnails(GetThumbnail());
|
|
|
|
if (_differentSettingsForSamePositionedLayers)
|
|
{
|
|
var layers = SlicerFile.SamePositionedLayers;
|
|
foreach (var layer in layers)
|
|
{
|
|
if(_samePositionedLayersLiftHeightEnabled) layer.LiftHeightTotal = (float) _samePositionedLayersLiftHeight;
|
|
if(_samePositionedLayersWaitTimeBeforeCureEnabled) layer.SetWaitTimeBeforeCureOrLightOffDelay((float) _samePositionedLayersWaitTimeBeforeCure);
|
|
}
|
|
}
|
|
|
|
new OperationMove(SlicerFile).Execute(progress);
|
|
|
|
return !progress.Token.IsCancellationRequested;
|
|
}
|
|
|
|
#endregion
|
|
} |