Files
UVtools/UVtools.Core/Operations/OperationCalculator.cs
T
Tiago Conceição 2d79c6504f v2.15.1
- **(Improvement) CWS:**
   - Remove light-off delay from the format
   - Sync movements with a delay time
   - Auto convert the light-off delay time to wait before cure time when required
- **(Improvement) CTB:**
   - When positively set the 'Wait time before cure' property on a CTBv3 or lower, it will compute the right light-off delay with that extra time into consideration
   - When positively set any of the light-off delays on a CTBv4 it will auto zero the 'Wait times' properties and vice-versa
   - Automation to set light-off delay on file load, will no longer do when any of 'Wait times' are defined for a CTBv4
- (Improvement) PrusaSlicer printers that use .cws format, implement the wait times on printer notes
- (Fix) GCode parser: Commented commands were being parsed
- (Fix) Exposure time information on bottom status bar was inverted, showing normal/bottom time instead of bottom/normal
- (Fix) macOS: Installing libusb is no longer a requirement
2021-07-24 02:55:43 +01:00

418 lines
15 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 System;
using System.Drawing;
using UVtools.Core.FileFormats;
using UVtools.Core.Objects;
namespace UVtools.Core.Operations
{
[Serializable]
public class OperationCalculator : Operation
{
#region Overrides
public override string Title => "Calculator";
public override string Description => null;
public override string ConfirmationText => null;
public override string ProgressTitle => null;
public override string ProgressAction => null;
public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None;
public override bool CanROI => false;
public override bool CanHaveProfiles => false;
#endregion
#region Properties
public MillimetersToPixels CalcMillimetersToPixels { get; set; }
public LightOffDelayC CalcLightOffDelay { get; set; }
public OptimalModelTilt CalcOptimalModelTilt { get; set; }
#endregion
#region Constructor
public OperationCalculator() { }
public OperationCalculator(FileFormat slicerFile) : base(slicerFile)
{ }
public override void InitWithSlicerFile()
{
base.InitWithSlicerFile();
CalcMillimetersToPixels = new MillimetersToPixels(SlicerFile.Resolution, SlicerFile.Display);
CalcLightOffDelay = new LightOffDelayC(
(decimal)SlicerFile.LiftHeight, (decimal)SlicerFile.BottomLiftHeight,
(decimal)SlicerFile.LiftSpeed, (decimal)SlicerFile.BottomLiftSpeed,
(decimal)SlicerFile.RetractSpeed, (decimal)SlicerFile.RetractSpeed);
CalcOptimalModelTilt = new OptimalModelTilt(SlicerFile.Resolution, SlicerFile.Display,
(decimal)SlicerFile.LayerHeight);
}
#endregion
public abstract class Calculation : BindableBase
{
public abstract string Description { get; }
public abstract string Formula { get; }
}
public sealed class MillimetersToPixels : Calculation
{
private uint _resolutionX;
private uint _resolutionY;
private decimal _displayWidth;
private decimal _displayHeight;
private decimal _millimeters = 1;
public override string Description => "Converts from Millimeters to Pixels";
public override string Formula => "Pixels = Resolution / Display * Millimeters";
public MillimetersToPixels(Size resolution, SizeF display, decimal millimeters = 1)
{
_resolutionX = (uint) resolution.Width;
_resolutionY = (uint) resolution.Height;
_displayWidth = (decimal) display.Width;
_displayHeight = (decimal) display.Height;
_millimeters = millimeters;
}
public uint ResolutionX
{
get => _resolutionX;
set
{
if(!RaiseAndSetIfChanged(ref _resolutionX, value)) return;
RaisePropertyChanged(nameof(PixelsPerMillimeterX));
RaisePropertyChanged(nameof(PixelsX));
}
}
public uint ResolutionY
{
get => _resolutionY;
set
{
if(!RaiseAndSetIfChanged(ref _resolutionY, value)) return;
RaisePropertyChanged(nameof(PixelsPerMillimeterY));
RaisePropertyChanged(nameof(PixelsY));
}
}
public decimal DisplayWidth
{
get => _displayWidth;
set
{
if(!RaiseAndSetIfChanged(ref _displayWidth, value)) return;
RaisePropertyChanged(nameof(PixelsPerMillimeterX));
RaisePropertyChanged(nameof(PixelsX));
}
}
public decimal DisplayHeight
{
get => _displayHeight;
set
{
if(!RaiseAndSetIfChanged(ref _displayHeight, value)) return;
RaisePropertyChanged(nameof(PixelsPerMillimeterY));
RaisePropertyChanged(nameof(PixelsY));
}
}
public decimal Millimeters
{
get => _millimeters;
set
{
if(!RaiseAndSetIfChanged(ref _millimeters, value)) return;
RaisePropertyChanged(nameof(PixelsX));
RaisePropertyChanged(nameof(PixelsY));
}
}
public decimal PixelsPerMillimeterX => DisplayWidth > 0 ? Math.Round(ResolutionX / DisplayWidth, 2) : 0;
public decimal PixelsPerMillimeterY => DisplayHeight > 0 ? Math.Round(ResolutionY / DisplayHeight, 2) : 0;
public decimal PixelsX => Math.Round(PixelsPerMillimeterX * Millimeters, 2);
public decimal PixelsY => Math.Round(PixelsPerMillimeterY * Millimeters, 2);
}
public sealed class LightOffDelayC : Calculation
{
private decimal _liftHeight;
private decimal _bottomLiftHeight;
private decimal _liftSpeed;
private decimal _bottomLiftSpeed;
private decimal _retractSpeed;
private decimal _bottomRetractSpeed;
private decimal _waitTime = 2.5m;
private decimal _bottomWaitTime = 3m;
public override string Description =>
"Calculates the required light-off delay (Moving time from the build plate + additional time for resin to stabilize) given the lifting height, speed and retract to wait x seconds before cure a new layer.\n" +
"Light-off delay is crucial for gaining higher-resolution and sharper prints.\n" +
"When the build plate retracts, it is important to allow enough time for the resin to stabilize before the UV lights turn on. This would ideally be around 2-3s.";
public override string Formula => "Light-off delay = Lifting height / (Lifting speed / 60) + Lifting height / (Retract speed / 60) + Desired wait seconds";
public decimal LiftHeight
{
get => _liftHeight;
set
{
if(!RaiseAndSetIfChanged(ref _liftHeight, value)) return;
RaisePropertyChanged(nameof(LightOffDelay));
}
}
public decimal BottomLiftHeight
{
get => _bottomLiftHeight;
set
{
if(!RaiseAndSetIfChanged(ref _bottomLiftHeight, value)) return;
RaisePropertyChanged(nameof(BottomLightOffDelay));
}
}
public decimal LiftSpeed
{
get => _liftSpeed;
set
{
if(!RaiseAndSetIfChanged(ref _liftSpeed, value)) return;
RaisePropertyChanged(nameof(LightOffDelay));
}
}
public decimal BottomLiftSpeed
{
get => _bottomLiftSpeed;
set
{
if(!RaiseAndSetIfChanged(ref _bottomLiftSpeed, value)) return;
RaisePropertyChanged(nameof(BottomLightOffDelay));
}
}
public decimal RetractSpeed
{
get => _retractSpeed;
set
{
if(!RaiseAndSetIfChanged(ref _retractSpeed, value)) return;
RaisePropertyChanged(nameof(LightOffDelay));
BottomRetractSpeed = _retractSpeed;
}
}
public decimal BottomRetractSpeed
{
get => _bottomRetractSpeed;
set
{
if (!RaiseAndSetIfChanged(ref _bottomRetractSpeed, value)) return;
RaisePropertyChanged(nameof(BottomLightOffDelay));
}
}
public decimal WaitTime
{
get => _waitTime;
set
{
if(!RaiseAndSetIfChanged(ref _waitTime, value)) return;
RaisePropertyChanged(nameof(LightOffDelay));
}
}
public decimal BottomWaitTime
{
get => _bottomWaitTime;
set
{
if (!RaiseAndSetIfChanged(ref _bottomWaitTime, value)) return;
RaisePropertyChanged(nameof(BottomLightOffDelay));
}
}
public decimal LightOffDelay => CalculateSeconds(_liftHeight, _liftSpeed, _retractSpeed, _waitTime);
public decimal BottomLightOffDelay => CalculateSeconds(_bottomLiftHeight, _bottomLiftSpeed, _bottomRetractSpeed, _bottomWaitTime);
public LightOffDelayC()
{
}
public LightOffDelayC(decimal liftHeight, decimal bottomLiftHeight, decimal liftSpeed, decimal bottomLiftSpeed, decimal retractSpeed, decimal bottomRetractSpeed, decimal waitTime = 2.5m, decimal bottomWaitTime = 3m)
{
_liftHeight = liftHeight;
_bottomLiftHeight = bottomLiftHeight;
_liftSpeed = liftSpeed;
_bottomLiftSpeed = bottomLiftSpeed;
_retractSpeed = retractSpeed;
_bottomRetractSpeed = bottomRetractSpeed;
_waitTime = waitTime;
_bottomWaitTime = bottomWaitTime;
}
public static decimal CalculateSeconds(decimal liftHeight, decimal liftSpeed, decimal retractSpeed, decimal extraWaitTime = 0)
{
var time = extraWaitTime;
if (liftSpeed > 0)
{
time += liftHeight / (liftSpeed / 60m);
}
if (retractSpeed > 0)
{
time += liftHeight / (retractSpeed / 60m);
}
return Math.Round(time, 2);
}
public static float CalculateSeconds(float liftHeight, float liftSpeed, float retractSpeed, float extraWaitTime = 0)
{
var time = extraWaitTime;
if (liftSpeed > 0)
{
time += liftHeight / (liftSpeed / 60f);
}
if (retractSpeed > 0)
{
time += liftHeight / (retractSpeed / 60f);
}
return (float)Math.Round(time, 2);
}
public static uint CalculateMilliseconds(float liftHeight, float liftSpeed, float retract, float extraWaitTime = 0) =>
(uint)(CalculateSeconds(liftHeight, liftSpeed, retract, extraWaitTime) * 1000);
public static float CalculateSecondsLiftOnly(float liftHeight, float liftSpeed, float extraWaitTime = 0)
{
if (liftSpeed <= 0) return extraWaitTime;
return (float)Math.Round(liftHeight / (liftSpeed / 60f) + extraWaitTime, 2);
}
public static uint CalculateMillisecondsLiftOnly(float liftHeight, float liftSpeed, float extraWaitTime = 0) =>
(uint)(CalculateSecondsLiftOnly(liftHeight, liftSpeed, extraWaitTime) * 1000);
}
public sealed class OptimalModelTilt : Calculation
{
private uint _resolutionX;
private uint _resolutionY;
private decimal _displayWidth;
private decimal _displayHeight;
private decimal _layerHeight = 0.05m;
public override string Description => "Calculates the optimal model tilt angle for printing and to minimize the visual layer effect.";
public override string Formula => "Angleº = tanh(Layer height / XYResolution) * (180 / PI)";
public OptimalModelTilt(Size resolution, SizeF display, decimal layerHeight = 0.05m)
{
_resolutionX = (uint)resolution.Width;
_resolutionY = (uint)resolution.Height;
_displayWidth = (decimal)display.Width;
_displayHeight = (decimal)display.Height;
_layerHeight = layerHeight;
}
public uint ResolutionX
{
get => _resolutionX;
set
{
if (!RaiseAndSetIfChanged(ref _resolutionX, value)) return;
RaisePropertyChanged(nameof(XYResolution));
RaisePropertyChanged(nameof(XYResolutionUm));
RaisePropertyChanged(nameof(TiltAngleDegrees));
}
}
public uint ResolutionY
{
get => _resolutionY;
set
{
if (!RaiseAndSetIfChanged(ref _resolutionY, value)) return;
RaisePropertyChanged(nameof(XYResolution));
RaisePropertyChanged(nameof(XYResolutionUm));
RaisePropertyChanged(nameof(TiltAngleDegrees));
}
}
public decimal DisplayWidth
{
get => _displayWidth;
set
{
if (!RaiseAndSetIfChanged(ref _displayWidth, value)) return;
RaisePropertyChanged(nameof(XYResolution));
RaisePropertyChanged(nameof(XYResolutionUm));
RaisePropertyChanged(nameof(TiltAngleDegrees));
}
}
public decimal DisplayHeight
{
get => _displayHeight;
set
{
if (!RaiseAndSetIfChanged(ref _displayHeight, value)) return;
RaisePropertyChanged(nameof(XYResolution));
RaisePropertyChanged(nameof(XYResolutionUm));
RaisePropertyChanged(nameof(TiltAngleDegrees));
}
}
public decimal LayerHeight
{
get => _layerHeight;
set
{
if (!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(XYResolution));
RaisePropertyChanged(nameof(XYResolutionUm));
RaisePropertyChanged(nameof(TiltAngleDegrees));
}
}
public decimal XYResolution => DisplayWidth > 0 || DisplayHeight > 0 ?
Math.Max(
DisplayWidth / ResolutionX,
DisplayHeight / ResolutionY
)
: 0;
public decimal XYResolutionUm => Math.Round(XYResolution * 1000, 2);
public decimal TiltAngleDegrees =>
XYResolution > 0 ? (decimal) Math.Round(Math.Tanh((double) (_layerHeight / XYResolution)) * (180 / Math.PI), 3) : 0;
}
}
}