diff options
Diffstat (limited to 'UVtools.Core/Operations')
-rw-r--r-- | UVtools.Core/Operations/Operation.cs | 28 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationDoubleExposure.cs | 2 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationDynamicLayerHeight.cs | 7 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs | 39 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationTimelapse.cs | 418 |
5 files changed, 471 insertions, 23 deletions
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index cee3c13..ad35b05 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -10,6 +10,7 @@ using System; using System.Drawing; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Serialization; using Emgu.CV; @@ -320,7 +321,14 @@ namespace UVtools.Core.Operations return string.IsNullOrWhiteSpace(message); } - public virtual string ValidateInternally() => null; + public virtual string ValidateInternally() + { + if (!ValidateSpawn(out var message)) + { + return message; + } + return null; + } /// <summary> /// Validates the operation @@ -604,6 +612,24 @@ namespace UVtools.Core.Operations #region Static Methods + public static Operation Deserialize(string path) + { + if (!File.Exists(path)) return null; + + var fileText = File.ReadAllText(path); + var match = Regex.Match(fileText, @"(?:<\/\s*Operation)([a-zA-Z0-9_]+)(?:\s*>)"); + if (!match.Success) return null; + if (match.Groups.Count < 1) return null; + var operationName = match.Groups[1].Value; + var baseType = typeof(Operation).FullName; + if (string.IsNullOrWhiteSpace(baseType)) return null; + var classname = baseType + operationName + ", UVtools.Core"; + var type = Type.GetType(classname); + if (type is null) return null; + + return Deserialize(path, type); + } + public static Operation Deserialize(string path, Type type) { XmlSerializer serializer = new(type); diff --git a/UVtools.Core/Operations/OperationDoubleExposure.cs b/UVtools.Core/Operations/OperationDoubleExposure.cs index 8c11c6e..dae57bf 100644 --- a/UVtools.Core/Operations/OperationDoubleExposure.cs +++ b/UVtools.Core/Operations/OperationDoubleExposure.cs @@ -59,7 +59,7 @@ namespace UVtools.Core.Operations public override string ValidateSpawn() { - if (!SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerExposureTime) + if (!SlicerFile.CanUseLayerPositionZ || !SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerExposureTime) { return NotSupportedMessage; } diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs index a9ae826..f761640 100644 --- a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs +++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs @@ -142,10 +142,15 @@ namespace UVtools.Core.Operations public override string ValidateSpawn() { + if (!SlicerFile.CanUseLayerPositionZ || !SlicerFile.CanUseLayerExposureTime) + { + return NotSupportedMessage; + } + if (SlicerFile.LayerHeight * 2 > FileFormat.MaximumLayerHeight) { return $"This file already uses the maximum layer height possible ({SlicerFile.LayerHeight}mm).\n" + - $"Layers can not be stacked, please re-slice your file with the lowest layer height of 0.01mm."; + "Layers can not be stacked, please re-slice your file with the lowest layer height of 0.01mm."; } for (uint layerIndex = 1; layerIndex < SlicerFile.LayerCount; layerIndex++) diff --git a/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs b/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs index 387b43c..d545b5c 100644 --- a/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs +++ b/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs @@ -19,7 +19,6 @@ namespace UVtools.Core.Operations public class OperationRaiseOnPrintFinish : Operation { #region Constants - public const byte DummyPixelBrightness = 128; #endregion #region Members @@ -30,8 +29,6 @@ namespace UVtools.Core.Operations #region Overrides - public override bool CanRunInPartialMode => true; - public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; public override string Title => "Raise platform on print finish"; @@ -51,11 +48,23 @@ namespace UVtools.Core.Operations public override string ValidateSpawn() { - if(!SlicerFile.CanUseLayerLiftHeight) + 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; } @@ -150,34 +159,24 @@ namespace UVtools.Core.Operations protected override bool ExecuteInternally(OperationProgress progress) { - return Execute(null, null); - //return !progress.Token.IsCancellationRequested; - } - - public override bool Execute(Mat mat, params object[] arguments) - { var layer = SlicerFile.LastLayer.Clone(); layer.PositionZ = (float)_positionZ; - layer.ExposureTime = 0.05f; // Very low exposure time + 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 = EmguExtensions.InitMat(SlicerFile.Resolution); - if(_outputDummyPixel) - { - newMat.SetByte(newMat.GetPixelPos(layer.BoundingRectangle.Center()), DummyPixelBrightness); - } - + using var newMat = _outputDummyPixel + ? SlicerFile.CreateMatWithDummyPixel(layer.BoundingRectangle.Center()) + : SlicerFile.CreateMat(); + layer.LayerMat = newMat; SlicerFile.SuppressRebuildPropertiesWork(() => { SlicerFile.LayerManager.Append(layer); - return true; }); - return true; + //return !progress.Token.IsCancellationRequested; } - #endregion } } diff --git a/UVtools.Core/Operations/OperationTimelapse.cs b/UVtools.Core/Operations/OperationTimelapse.cs new file mode 100644 index 0000000..084ea57 --- /dev/null +++ b/UVtools.Core/Operations/OperationTimelapse.cs @@ -0,0 +1,418 @@ +/* + * 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.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using UVtools.Core.FileFormats; +using UVtools.Core.Layers; + +namespace UVtools.Core.Operations +{ + [Serializable] + public class OperationTimelapse : Operation + { + #region Constants + #endregion + + #region Enums + public enum TimelapseRaiseMode + { + [Description("Lift height: Use the lift sequence to raise to the set postion (Faster)")] + LiftHeight, + + [Description("Virtual layer: Print a blank layer to simulate and raise to the set position (Slower)")] + VirtualLayer, + } + #endregion + + #region Members + private decimal _raisePositionZ; + private bool _outputDummyPixel = true; + private decimal _raiseEachNthHeight = 1; + private TimelapseRaiseMode _raiseMode = TimelapseRaiseMode.LiftHeight; + private decimal _waitTimeAfterLift = 1; + private decimal _exposureTime = 1; + private bool _useCustomLift; + private decimal _slowLiftHeight = 3; + private decimal _liftSpeed; + private decimal _liftSpeed2; + private decimal _slowRetractHeight = 3; + private decimal _retractSpeed; + private decimal _retractSpeed2; + + #endregion + + #region Overrides + + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Normal; + + public override string Title => "Timelapse"; + public override string Description => + "Raise the build platform to a set position every odd-even height to be able to take a photo and create a time-lapse video of the print.\n" + + "You will require external hardware to take the photos, and create the time-lapse video by your own.\n" + + "NOTE: Only use this tool once. It will delay the total print time significantly."; + + public override string ConfirmationText => + $"raise the platform at every odd-even {_raiseEachNthHeight}mm to Z={_raisePositionZ}mm"; + + public override string ProgressTitle => "Raising layers"; + + public override string ProgressAction => "Raised layer"; + + public override string ValidateSpawn() + { + if(!SlicerFile.CanUseLayerPositionZ && !SlicerFile.CanUseLayerLiftHeight) + { + return NotSupportedMessage; + } + + return null; + } + + public override string ValidateInternally() + { + var sb = new StringBuilder(); + + if (!ValidateSpawn(out var message)) + { + sb.AppendLine(message); + } + + if ((_raiseMode == TimelapseRaiseMode.LiftHeight && !SlicerFile.CanUseLayerLiftHeight) || + (_raiseMode == TimelapseRaiseMode.VirtualLayer && !SlicerFile.CanUseLayerPositionZ)) + { + return $"The raise method {_raiseMode} is not compatible with this printer / file format, please choose other method."; + } + + return sb.ToString(); + } + + public override string ToString() + { + var result = $"[Mode: {_raiseMode}] [Z={_raisePositionZ}mm] [Raise each: {_raiseEachNthHeight}mm]"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion + + #region Properties + + /// <summary> + /// Gets the minimum possible + /// </summary> + public float MinimumPositionZ => Layer.RoundHeight(SlicerFile.PrintHeight + SlicerFile.LayerHeight); + + public TimelapseRaiseMode RaiseMode + { + get => _raiseMode; + set + { + if(!RaiseAndSetIfChanged(ref _raiseMode, value)) return; + RaisePropertyChanged(nameof(IsLiftHeightMode)); + RaisePropertyChanged(nameof(IsVirtualLayerMode)); + } + } + + public bool IsLiftHeightMode => _raiseMode is TimelapseRaiseMode.LiftHeight; + public bool IsVirtualLayerMode => _raiseMode is TimelapseRaiseMode.VirtualLayer; + + /// <summary> + /// Sets or gets the Z position to raise to + /// </summary> + public decimal RaisePositionZ + { + get => _raisePositionZ; + set => RaiseAndSetIfChanged(ref _raisePositionZ, Layer.RoundHeight(Math.Clamp(value, 10, 10000))); + } + + /// <summary> + /// True to output a dummy pixel on bounding rectangle position to avoid empty layer and blank image, otherwise set to false + /// </summary> + public bool OutputDummyPixel + { + get => _outputDummyPixel; + set => RaiseAndSetIfChanged(ref _outputDummyPixel, value); + } + + /// <summary> + /// Gets or sets the alternating height in millimeters to raise when, it will raise only at each defined millimeters and skip the same next millimeters + /// </summary> + public decimal RaiseEachNthHeight + { + get => _raiseEachNthHeight; + set => RaiseAndSetIfChanged(ref _raiseEachNthHeight, Math.Max(0, value)); + } + + public ushort RaiseEachNthLayers + { + get + { + if (_raiseEachNthHeight == 0) return 1; + return (ushort)Math.Max(1, _raiseEachNthHeight / (decimal)SlicerFile.LayerHeight); + } + } + + public decimal WaitTimeAfterLift + { + get => _waitTimeAfterLift; + set => RaiseAndSetIfChanged(ref _waitTimeAfterLift, Math.Max(0, value)); + } + + public decimal ExposureTime + { + get => _exposureTime; + set => RaiseAndSetIfChanged(ref _exposureTime, Math.Max(0, value)); + } + + public bool UseCustomLift + { + get => _useCustomLift; + set => RaiseAndSetIfChanged(ref _useCustomLift, value); + } + + public decimal SlowLiftHeight + { + get => _slowLiftHeight; + set => RaiseAndSetIfChanged(ref _slowLiftHeight, Math.Max(0, value)); + } + + public decimal LiftSpeed + { + get => _liftSpeed; + set => RaiseAndSetIfChanged(ref _liftSpeed, Math.Max(0, value)); + } + + public decimal LiftSpeed2 + { + get => _liftSpeed2; + set => RaiseAndSetIfChanged(ref _liftSpeed2, Math.Max(0, value)); + } + + public decimal SlowRetractHeight + { + get => _slowRetractHeight; + set => RaiseAndSetIfChanged(ref _slowRetractHeight, Math.Max(0, value)); + } + + public decimal RetractSpeed + { + get => _retractSpeed; + set => RaiseAndSetIfChanged(ref _retractSpeed, Math.Max(0, value)); + } + + public decimal RetractSpeed2 + { + get => _retractSpeed2; + set => RaiseAndSetIfChanged(ref _retractSpeed2, Math.Max(0, value)); + } + + #endregion + + #region Constructor + + public OperationTimelapse() + { + } + + public OperationTimelapse(FileFormat slicerFile) : base(slicerFile) + { + if (_raisePositionZ <= 0) _raisePositionZ = (decimal)SlicerFile.MachineZ; + if(_exposureTime <= 0) _exposureTime = SlicerFile.SupportsGCode ? 0 : 0.05M; + + if (_liftSpeed <= 0) _liftSpeed = (decimal) SlicerFile.LiftSpeed; + if (_liftSpeed2 <= 0) _liftSpeed2 = (decimal) SlicerFile.MaximumSpeed; + + if (SlicerFile.CanUseLayerRetractSpeed2) // TSMC + { + if (_retractSpeed <= 0) _retractSpeed = (decimal)SlicerFile.MaximumSpeed; + if (_retractSpeed2 <= 0) _retractSpeed2 = (decimal)SlicerFile.RetractSpeed2; + } + else + { + if (_retractSpeed <= 0) _retractSpeed = (decimal)SlicerFile.RetractSpeed; + } + + } + + #endregion + + #region Equality + + protected bool Equals(OperationTimelapse other) + { + return _raisePositionZ == other._raisePositionZ && _outputDummyPixel == other._outputDummyPixel && _raiseEachNthHeight == other._raiseEachNthHeight && _raiseMode == other._raiseMode && _waitTimeAfterLift == other._waitTimeAfterLift && _exposureTime == other._exposureTime && _useCustomLift == other._useCustomLift && _slowLiftHeight == other._slowLiftHeight && _liftSpeed == other._liftSpeed && _liftSpeed2 == other._liftSpeed2 && _slowRetractHeight == other._slowRetractHeight && _retractSpeed == other._retractSpeed && _retractSpeed2 == other._retractSpeed2; + } + + 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((OperationTimelapse) obj); + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(_raisePositionZ); + hashCode.Add(_outputDummyPixel); + hashCode.Add(_raiseEachNthHeight); + hashCode.Add((int) _raiseMode); + hashCode.Add(_waitTimeAfterLift); + hashCode.Add(_exposureTime); + hashCode.Add(_useCustomLift); + hashCode.Add(_slowLiftHeight); + hashCode.Add(_liftSpeed); + hashCode.Add(_liftSpeed2); + hashCode.Add(_slowRetractHeight); + hashCode.Add(_retractSpeed); + hashCode.Add(_retractSpeed2); + return hashCode.ToHashCode(); + } + + #endregion + + #region Methods + + public void OptimizeRaisePositionZ() + { + RaisePositionZ = (decimal) Math.Min(SlicerFile.MachineZ, SlicerFile.PrintHeight + 5); + } + + public void MaxRaisePositionZ() + { + RaisePositionZ = (decimal) SlicerFile.MachineZ; + } + + protected override bool ExecuteInternally(OperationProgress progress) + { + var virtualLayers = new List<uint>(); + float checkpointHeight = SlicerFile[0].PositionZ; + for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) + { + progress++; + var layer = SlicerFile[layerIndex]; + if (_raiseEachNthHeight > 0 && (decimal)Layer.RoundHeight(layer.PositionZ - checkpointHeight) < _raiseEachNthHeight) continue; + if ((decimal)layer.PositionZ >= _raisePositionZ) break; // pass the target height, do not continue + checkpointHeight = layer.PositionZ; + + switch (_raiseMode) + { + case TimelapseRaiseMode.LiftHeight: + if (_useCustomLift) + { + layer.LiftSpeed = (float)_liftSpeed; + layer.RetractSpeed = (float)_retractSpeed; + + if (SlicerFile.CanUseLayerLiftHeight2) + { + layer.LiftHeight = (float)_slowLiftHeight; + } + + if (SlicerFile.CanUseLayerLiftSpeed2) + { + layer.LiftSpeed2 = (float)_liftSpeed2; + } + + if (SlicerFile.CanUseLayerRetractHeight2) + { + layer.RetractHeight2 = (float)_slowRetractHeight; + } + + if (SlicerFile.CanUseLayerRetractSpeed2) + { + layer.RetractSpeed2 = (float)_retractSpeed2; + } + } + + if (SlicerFile.CanUseLayerLiftHeight2 && (layer.LiftHeight2 > 0 || _useCustomLift && _slowLiftHeight > 0)) // TSMC + { + layer.LiftHeight2 = Math.Max(0, (float)_raisePositionZ - layer.PositionZ - layer.LiftHeight); + } + else + { + layer.LiftHeightTotal = Math.Max(layer.LiftHeightTotal, (float)_raisePositionZ - layer.PositionZ); + } + + if (SlicerFile.CanUseLayerWaitTimeAfterLift && _waitTimeAfterLift > 0) + { + layer.WaitTimeAfterLift = (float) _waitTimeAfterLift; + } + + break; + case TimelapseRaiseMode.VirtualLayer: + virtualLayers.Add(layerIndex); + break; + default: + throw new ArgumentOutOfRangeException(nameof(RaiseMode)); + } + } + + if (virtualLayers.Count > 0 && _raiseMode == TimelapseRaiseMode.VirtualLayer) + { + using var mat = _outputDummyPixel + ? SlicerFile.CreateMatWithDummyPixel() + : SlicerFile.CreateMat(); + + var layer = new Layer(SlicerFile.LayerCount, mat, SlicerFile) + { + PositionZ = (float) _raisePositionZ, + ExposureTime = (float) _exposureTime, + // This layer does not require a lift procedure + LiftHeightTotal = SlicerFile.SupportsGCode ? 0 : 0.1f, + LiftSpeed = SlicerFile.MaximumSpeed, + LiftSpeed2 = SlicerFile.MaximumSpeed, + RetractSpeed = SlicerFile.MaximumSpeed, + RetractSpeed2 = SlicerFile.MaximumSpeed, + RetractHeight2 = 0 + }; + + layer.SetNoDelays(); + + /*if (_useCustomLift) + { + layer.LiftSpeed = (float) _liftSpeed; + layer.RetractSpeed = (float) _retractSpeed; + + if (SlicerFile.CanUseLayerLiftSpeed2) + { + layer.LiftSpeed2 = (float) _liftSpeed2; + } + + if (SlicerFile.CanUseLayerRetractSpeed2) + { + layer.RetractSpeed2 = (float) _retractSpeed2; + } + }*/ + + var layers = SlicerFile.ToList(); + for (int i = 0; i < virtualLayers.Count; i++) + { + /*var newLayer = layer.Clone(); + if (!_useCustomLift) + { + var intersectLayer = Math.Clamp(virtualLayers[i] + i, 0, SlicerFile.LastLayerIndex); + SlicerFile[intersectLayer].CopyLiftTo(newLayer); + // This layer does not require a lift procedure + newLayer.LiftHeightTotal = SlicerFile.SupportsGCode ? 0 : 0.1f; + newLayer.RetractHeight2 = 0; + }*/ + layers.Insert((int)(virtualLayers[i] + i), layer.Clone()); + } + SlicerFile.SuppressRebuildPropertiesWork(() => SlicerFile.LayerManager.Layers = layers.ToArray()); + } + + return !progress.Token.IsCancellationRequested; + } + + #endregion + } +} |