/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc.
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
using System;
using System.Text;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Layers;
namespace UVtools.Core.Operations;
public class OperationRaiseOnPrintFinish : Operation
{
#region Constants
#endregion
#region Members
private decimal _positionZ;
private decimal _waitTime = 1200; // 20m
private bool _outputDummyPixel = true;
#endregion
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
public override string IconClass => "fa-solid fa-level-up-alt";
public override string Title => "Raise platform on print finish";
public override string Description =>
"Raise the build platform to a set position after finish the print.\n\n" +
"NOTE: Only use this tool once and if your printer firmware don't already raise the build platform after finish the print.\n" +
"This will create a \"empty\" layer on end to simulate a print at a defined height.\n" +
"Not compatible with all printers, still it won't cause any harm if printer don't support this strategy.";
public override string ConfirmationText =>
$"raise the platform on print finish to Z={_positionZ}mm";
public override string ProgressTitle =>
$"Inserting dummy layer on end";
public override string ProgressAction => "Inserted layer";
public override string? ValidateSpawn()
{
if(!SlicerFile.CanUseLayerPositionZ)
{
return NotSupportedMessage;
}
if (SlicerFile.LayerCount >= 2)
{
var layerHeight = SlicerFile.LastLayer!.LayerHeight;
var criteria = Math.Max((float) Layer.MaximumHeight, SlicerFile.LayerHeight);
if (layerHeight > criteria)
{
return $"With a difference of {layerHeight}mm between the last two layers, it looks like this tool had already been applied.\n" +
$"The difference must be less or equal to {criteria}mm in order to run this tool.";
}
}
return null;
}
public override string? ValidateInternally()
{
var sb = new StringBuilder();
if (!ValidateSpawn(out var message))
{
sb.AppendLine(message);
}
if((float)_positionZ < SlicerFile.PrintHeight)
{
sb.AppendLine($"Can't raise to {_positionZ}mm, because it's below the maximum print height of {SlicerFile.PrintHeight}mm.");
}
else if ((float)_positionZ == SlicerFile.PrintHeight)
{
sb.AppendLine($"Raise to {_positionZ}mm will have no effect because it's the same height as last layer of {SlicerFile.PrintHeight}mm.");
}
return sb.ToString();
}
public override string ToString()
{
var result = $"[Z={_positionZ}mm] [Wait: {_waitTime}s] [Dummy pixel: {_outputDummyPixel}]";
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
#endregion
#region Properties
public float MinimumPositionZ => Layer.RoundHeight(SlicerFile.PrintHeight + SlicerFile.LayerHeight);
public float MediumPositionZ => Layer.RoundHeight(MinimumPositionZ + (MaximumPositionZ - MinimumPositionZ) / 2);
public float MaximumPositionZ => Math.Max(MinimumPositionZ, Layer.RoundHeight(SlicerFile.MachineZ));
///
/// Gets or sets the Z position to raise to
///
public decimal PositionZ
{
get => _positionZ;
set => RaiseAndSetIfChanged(ref _positionZ, Layer.RoundHeight(value));
}
///
/// Gets or sets the ensured wait time to stay still on the desired position.
/// This is useful if the printer firmware always move to top and you want to stay still on the set position for at least the desired time.
/// Note: The print time calculation will take this wait into consideration and display a longer print time.
///
public decimal WaitTime
{
get => _waitTime;
set => RaiseAndSetIfChanged(ref _waitTime, Math.Round(Math.Max(0, value), 2));
}
///
/// True to output a dummy pixel on bounding rectangle position to avoid empty layer and blank image, otherwise set to false
///
public bool OutputDummyPixel
{
get => _outputDummyPixel;
set => RaiseAndSetIfChanged(ref _outputDummyPixel, value);
}
#endregion
#region Constructor
public OperationRaiseOnPrintFinish()
{
//_outputDummyPixel = !SlicerFile.SupportsGCode;
}
public OperationRaiseOnPrintFinish(FileFormat slicerFile) : base(slicerFile)
{ }
public override void InitWithSlicerFile()
{
if (_positionZ <= 0) _positionZ = (decimal)SlicerFile.MachineZ;
}
#endregion
#region Equality
protected bool Equals(OperationRaiseOnPrintFinish other)
{
return _positionZ == other._positionZ && _outputDummyPixel == other._outputDummyPixel && _waitTime == other._waitTime;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((OperationRaiseOnPrintFinish) obj);
}
public override int GetHashCode()
{
return HashCode.Combine(_positionZ, _outputDummyPixel, _waitTime);
}
#endregion
#region Methods
public void SetToMinimumPosition() => PositionZ = (decimal)MinimumPositionZ;
public void SetToMediumPosition() => PositionZ = (decimal)MediumPositionZ;
public void SetToMaximumPosition() => PositionZ = (decimal)MaximumPositionZ;
public void SetWaitTime(decimal time) => WaitTime = time;
protected override bool ExecuteInternally(OperationProgress progress)
{
var layer = SlicerFile.LastLayer!.Clone();
layer.PositionZ = (float)_positionZ;
layer.ExposureTime = SlicerFile.SupportsGCode ? 0 : 0.05f; // Very low exposure time
layer.LightPWM = 0; // Try to disable light if possible
layer.SetNoDelays();
using var newMat = _outputDummyPixel
? SlicerFile.CreateMatWithDummyPixel(layer.BoundingRectangle.Center())
: SlicerFile.CreateMat();
layer.LayerMat = newMat;
if(_waitTime > 0) layer.SetWaitTimeBeforeCureOrLightOffDelay((float)_waitTime);
SlicerFile.SuppressRebuildPropertiesWork(() =>
{
SlicerFile.Append(layer);
});
return true;
//return !progress.Token.IsCancellationRequested;
}
#endregion
}