diff options
Diffstat (limited to 'UVtools.Core/Operations/OperationCalibrateStressTower.cs')
-rw-r--r-- | UVtools.Core/Operations/OperationCalibrateStressTower.cs | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs new file mode 100644 index 0000000..4d95608 --- /dev/null +++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs @@ -0,0 +1,437 @@ +/* + * 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 System; +using System.Drawing; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; +using UVtools.Core.Objects; + +namespace UVtools.Core.Operations +{ + [Serializable] + public sealed class OperationCalibrateStressTower : Operation + { + #region Members + private decimal _displayWidth; + private decimal _displayHeight; + private decimal _layerHeight = 0.05M; + private ushort _bottomLayers = 3; + private decimal _bottomExposure = 60; + private decimal _normalExposure = 12; + private decimal _baseDiameter = 30; + private decimal _baseHeight = 3; + private decimal _bodyHeight = 50; + private decimal _ceilHeight = 3; + private byte _chamferLayers = 6; + private bool _enableAntiAliasing = true; + private bool _mirrorOutput; + private byte _spirals = 2; + private decimal _spiralDiameter = 2; + private SpiralDirections _spiralDirection = SpiralDirections.Both; + private decimal _spiralAngleStepPerLayer = 1; + + #endregion + + #region Overrides + + public override bool CanROI => false; + + public override bool CanCancel => false; + + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; + + public override string Title => "Stress tower"; + public override string Description => + "Generates a stress tower to test the printer capabilities.\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 stress tower?"; + + public override string ProgressTitle => + $"Generating the stress tower"; + + public override string ProgressAction => "Generated"; + + public override StringTag Validate(params object[] parameters) + { + 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."); + } + + return new StringTag(sb.ToString()); + } + + public override string ToString() + { + var result = $"[Layer Height: {_layerHeight}] " + + $"[Bottom layers: {_bottomLayers}] " + + $"[Exposure: {_bottomExposure}/{_normalExposure}] " + + $"[Base: H:{_baseHeight} D:{_baseDiameter}] " + + $"[Ceil: {_ceilHeight}] [Body: {_bodyHeight}] " + + $"[Chamfer: {_chamferLayers}] " + + $"[Spirals: {_spirals} Dir: {_spiralDirection} D:{_spiralDiameter} Angle: {_spiralAngleStepPerLayer}º]" + + $"[AA: {_enableAntiAliasing}] [Mirror: {_mirrorOutput}]"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Properties + + [XmlIgnore] + public Size Resolution { get; set; } = Size.Empty; + + public decimal DisplayWidth + { + get => _displayWidth; + set + { + if(!RaiseAndSetIfChanged(ref _displayWidth, Math.Round(value, 2))) return; + } + } + + public decimal DisplayHeight + { + get => _displayHeight; + set + { + if(!RaiseAndSetIfChanged(ref _displayHeight, Math.Round(value, 2))) return; + } + } + + public decimal LayerHeight + { + get => _layerHeight; + set + { + if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return; + RaisePropertyChanged(nameof(BottomLayersMM)); + RaisePropertyChanged(nameof(LayerCount)); + } + } + + public ushort Microns => (ushort)(LayerHeight * 1000); + + public ushort BottomLayers + { + get => _bottomLayers; + set + { + if(!RaiseAndSetIfChanged(ref _bottomLayers, value)) return; + RaisePropertyChanged(nameof(BottomLayersMM)); + } + } + + public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2); + + 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 uint LayerCount => (uint) Math.Floor((_baseHeight + _bodyHeight + _ceilHeight) / LayerHeight); + + public decimal TotalHeight => _baseHeight + _bodyHeight + _ceilHeight; + + public decimal BaseDiameter + { + get => _baseDiameter; + set => RaiseAndSetIfChanged(ref _baseDiameter, value); + } + + public decimal BaseHeight + { + get => _baseHeight; + set + { + if(!RaiseAndSetIfChanged(ref _baseHeight, value)) return; + RaisePropertyChanged(nameof(TotalHeight)); + } + } + + public decimal BodyHeight + { + get => _bodyHeight; + set + { + if (!RaiseAndSetIfChanged(ref _bodyHeight, value)) return; + RaisePropertyChanged(nameof(TotalHeight)); + } + } + + public decimal CeilHeight + { + get => _ceilHeight; + set + { + if(!RaiseAndSetIfChanged(ref _ceilHeight, value)) return; + RaisePropertyChanged(nameof(TotalHeight)); + } + } + + public byte ChamferLayers + { + get => _chamferLayers; + set => RaiseAndSetIfChanged(ref _chamferLayers, value); + } + + public bool EnableAntiAliasing + { + get => _enableAntiAliasing; + set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value); + } + + public bool MirrorOutput + { + get => _mirrorOutput; + set => RaiseAndSetIfChanged(ref _mirrorOutput, value); + } + + public byte Spirals + { + get => _spirals; + set => RaiseAndSetIfChanged(ref _spirals, value); + } + + public decimal SpiralDiameter + { + get => _spiralDiameter; + set => RaiseAndSetIfChanged(ref _spiralDiameter, value); + } + + public SpiralDirections SpiralDirection + { + get => _spiralDirection; + set => RaiseAndSetIfChanged(ref _spiralDirection, value); + } + + public decimal SpiralAngleStepPerLayer + { + get => _spiralAngleStepPerLayer; + set => RaiseAndSetIfChanged(ref _spiralAngleStepPerLayer, value); + } + + #endregion + + #region Enums + + public enum SpiralDirections : byte + { + Clockwise, + Alternate, + Both + } + + public static Array SpiralDirectionsItems => Enum.GetValues(typeof(SpiralDirections)); + #endregion + + #region Equality + + private bool Equals(OperationCalibrateStressTower other) + { + return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _baseDiameter == other._baseDiameter && _baseHeight == other._baseHeight && _bodyHeight == other._bodyHeight && _ceilHeight == other._ceilHeight && _chamferLayers == other._chamferLayers && _enableAntiAliasing == other._enableAntiAliasing && _mirrorOutput == other._mirrorOutput && _spirals == other._spirals && _spiralDiameter == other._spiralDiameter && _spiralDirection == other._spiralDirection && _spiralAngleStepPerLayer == other._spiralAngleStepPerLayer; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationCalibrateStressTower 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(_baseDiameter); + hashCode.Add(_baseHeight); + hashCode.Add(_bodyHeight); + hashCode.Add(_ceilHeight); + hashCode.Add(_chamferLayers); + hashCode.Add(_enableAntiAliasing); + hashCode.Add(_mirrorOutput); + hashCode.Add(_spirals); + hashCode.Add(_spiralDiameter); + hashCode.Add((int) _spiralDirection); + hashCode.Add(_spiralAngleStepPerLayer); + return hashCode.ToHashCode(); + } + + #endregion + + #region Methods + public Mat[] GetLayers() + { + var layers = new Mat[LayerCount]; + + Slicer slicer = new(Resolution, new SizeF((float) DisplayWidth, (float) DisplayHeight)); + Point center = new Point(Resolution.Width / 2, Resolution.Height / 2); + uint baseRadius = slicer.PixelsFromMillimeters(_baseDiameter) / 2; + uint baseLayers = (ushort) Math.Floor(_baseHeight / _layerHeight); + uint bodyLayers = (ushort) Math.Floor(_bodyHeight / _layerHeight); + uint spiralLayers = (uint) Math.Floor(_spiralDiameter / _layerHeight); + uint ceilLayers = (ushort) Math.Floor(_ceilHeight / _layerHeight); + uint currrentlayer = baseLayers; + + decimal spiralOffsetAngle = 360m / _spirals; + uint spiralRadius = slicer.PixelsFromMillimeters(_spiralDiameter) / 2; + + /*const FontFace fontFace = FontFace.HersheyDuplex; + const double fontScale = 1; + const byte fontThickness = 2; + LineType lineType = _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected; + + var anchor = new Point(-1, -1); + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);*/ + Parallel.For(0, LayerCount, layerIndex => + { + layers[layerIndex] = EmguExtensions.InitMat(Resolution); + }); + + Parallel.For(0, baseLayers, layerIndex => + { + int chamferOffset = (int) Math.Max(0, _chamferLayers - layerIndex); + CvInvoke.Circle(layers[layerIndex], center, (int) baseRadius - chamferOffset, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + }); + + + Parallel.For(0, baseLayers+bodyLayers, layerIndex => + { + decimal angle = (layerIndex * _spiralAngleStepPerLayer) % 360m; + for (byte spiral = 0; spiral < _spirals; spiral++) + { + decimal spiralAngle = (spiralOffsetAngle * spiral + angle) % 360; + if (_spiralDirection == SpiralDirections.Alternate && spiral % 2 == 0) + { + spiralAngle = -spiralAngle; + } + Point location = new Point((int) (center.X - baseRadius + spiralRadius), center.Y); + var locationCW = location.Rotate((double) spiralAngle, center); + var locationCCW = location.Rotate((double) -spiralAngle, center); + + uint maxLayer = (uint) Math.Min(layerIndex + spiralLayers, baseLayers + bodyLayers); + + for (uint spiralLayerIndex = (uint) layerIndex; spiralLayerIndex < maxLayer; spiralLayerIndex++) + { + CvInvoke.Circle(layers[spiralLayerIndex], locationCW, (int)spiralRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + if (_spiralDirection == SpiralDirections.Both) + { + spiralAngle = -spiralAngle; + CvInvoke.Circle(layers[spiralLayerIndex], locationCCW, (int)spiralRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + } + } + } + }); + + currrentlayer += bodyLayers; + + Parallel.For(0, ceilLayers, i => + { + uint layerIndex = (uint)(currrentlayer + i); + CvInvoke.Circle(layers[layerIndex], center, (int)baseRadius, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + }); + + + + if (_mirrorOutput) + { + Parallel.ForEach(layers, mat => CvInvoke.Flip(mat, mat, FlipType.Horizontal)); + } + + 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, "Stress Tower", 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.White3Byte, fontThickness); + CvInvoke.PutText(thumbnail, $"{_spirals} Spirals @ {_spiralAngleStepPerLayer}deg", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + return thumbnail; + } + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerCount); + slicerFile.SuppressRebuildProperties = true; + + var newLayers = new Layer[LayerCount]; + + slicerFile.LayerHeight = (float)LayerHeight; + slicerFile.BottomExposureTime = (float)BottomExposure; + slicerFile.ExposureTime = (float)NormalExposure; + slicerFile.BottomLayerCount = BottomLayers; + + var layers = GetLayers(); + + Parallel.For(0, LayerCount, layerIndex => + { + newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], slicerFile.LayerManager); + layers[layerIndex].Dispose(); + lock (progress) + { + progress++; + } + }); + + slicerFile.LayerManager.Layers = newLayers; + slicerFile.LayerManager.RebuildLayersProperties(); + + /*var moveOp = new OperationMove(slicerFile.LayerManager.BoundingRectangle, slicerFile.Resolution) + { + IsCutMove = true, + LayerIndexEnd = slicerFile.LayerCount - 1 + }; + moveOp.Execute(slicerFile, progress);*/ + + if (slicerFile.ThumbnailsCount > 0) + slicerFile.SetThumbnails(GetThumbnail()); + + slicerFile.SuppressRebuildProperties = false; + + return true; + } + + #endregion + } +} |