/* * 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.Collections.Generic; using UVtools.Core.FileFormats; using UVtools.Core.Layers; using UVtools.Core.Operations; namespace UVtools.Core.GCode { public class GCodeLayer { private float? _positionZ; private float? _waitTimeBeforeCure; private float? _exposureTime; private float? _waitTimeAfterCure; private float? _liftHeight; private float? _liftSpeed; private float? _liftHeight2; private float? _liftSpeed2; private float? _waitTimeAfterLift; private float? _retractSpeed; private float? _retractHeight2; private float? _retractSpeed2; public enum GCodeLastParsedLine : byte { LayerIndex, } public bool IsValid => LayerIndex.HasValue; public FileFormat SlicerFile { get; } public List<(float Pos, float Speed)> Movements = new(); public uint? LayerIndex { get; set; } public float? PositionZ { get => _positionZ; set => _positionZ = value; } public float PreviousPositionZ { get; set; } public float? WaitTimeBeforeCure { get => _waitTimeBeforeCure; set => _waitTimeBeforeCure = value is null ? null : (float)Math.Round(value.Value, 2); } public float? ExposureTime { get => _exposureTime; set => _exposureTime = value is null ? null : (float)Math.Round(value.Value, 2); } public float? WaitTimeAfterCure { get => _waitTimeAfterCure; set => _waitTimeAfterCure = value is null ? null : (float)Math.Round(value.Value, 2); } public float? LiftHeight { get => _liftHeight; set => _liftHeight = value is null ? null : Layer.RoundHeight(value.Value); } public float? LiftSpeed { get => _liftSpeed; set => _liftSpeed = value is null ? null : (float)Math.Round(value.Value, 2); } public float LiftHeightTotal => Layer.RoundHeight((LiftHeight ?? 0) + (LiftHeight2 ?? 0)); public float? LiftHeight2 { get => _liftHeight2; set => _liftHeight2 = value is null ? null : Layer.RoundHeight(value.Value); } public float? LiftSpeed2 { get => _liftSpeed2; set => _liftSpeed2 = value is null ? null : (float)Math.Round(value.Value, 2); } public float? WaitTimeAfterLift { get => _waitTimeAfterLift; set => _waitTimeAfterLift = value is null ? null : (float)Math.Round(value.Value, 2); } public float? RetractSpeed { get => _retractSpeed; set => _retractSpeed = value is null ? null : (float)Math.Round(value.Value, 2); } public float? RetractHeight2 { get => _retractHeight2; set => _retractHeight2 = value is null ? null : Layer.RoundHeight(value.Value); } public float? RetractSpeed2 { get => _retractSpeed2; set => _retractSpeed2 = value is null ? null : (float)Math.Round(value.Value, 2); } public byte? LightPWM { get; set; } public bool IsExposing => LightPWM.HasValue && !IsAfterLightOff; public bool IsExposed => LightPWM.HasValue && IsAfterLightOff; public byte LightOffCount { get; set; } public bool IsAfterLightOff => LightOffCount > 0; public GCodeLayer(FileFormat slicerFile) { SlicerFile = slicerFile; } public void Init() { PreviousPositionZ = PositionZ ?? 0; Movements.Clear(); LayerIndex = null; PositionZ = null; WaitTimeBeforeCure = null; ExposureTime = null; WaitTimeAfterCure = null; LiftHeight = null; LiftSpeed = null; LiftHeight2 = null; LiftSpeed2 = null; WaitTimeAfterLift = null; RetractSpeed = null; RetractHeight2 = null; RetractSpeed2 = null; LightPWM = null; LightOffCount = 0; } public void AssignMovements(GCodeBuilder.GCodePositioningTypes positionType) { if (Movements.Count == 0) return; var currentZ = PreviousPositionZ; PositionZ = null; LiftHeight = null; LiftSpeed = null; LiftHeight2 = null; LiftSpeed2 = null; RetractSpeed = null; RetractHeight2 = null; RetractSpeed2 = null; for (int i = 0; i < Movements.Count; i++) { var (pos, speed) = Movements[i]; float heightRaw; switch (positionType) { case GCodeBuilder.GCodePositioningTypes.Absolute: heightRaw = Layer.RoundHeight(pos - currentZ); currentZ = pos; break; case GCodeBuilder.GCodePositioningTypes.Partial: heightRaw = pos; currentZ = Layer.RoundHeight(currentZ + pos); break; default: throw new ArgumentOutOfRangeException(nameof(positionType)); } // Fail-safe check if (currentZ < PreviousPositionZ) throw new NotSupportedException("GCode parsing error: Attempting to crash the print on the LCD with negative position.\n" + "Do not attempt to print this file!"); // Is position Z if (i == Movements.Count - 1) { PositionZ = currentZ; if (LiftHeight.HasValue) { RetractSpeed = speed; // A lift exists, set to retract speed of this move } continue; } if (heightRaw == 0) continue; var height = Math.Abs(heightRaw); if (heightRaw > 0) // Is a lift { if (!LiftHeight.HasValue) { LiftHeight = height; LiftSpeed = speed; continue; } LiftHeight2 ??= 0; LiftHeight2 += height; LiftSpeed2 = speed; continue; } if(!LiftHeight.HasValue) continue; // Fail-safe: Retract without a lift? Skip // Is a extra retract (2) RetractHeight2 ??= 0; RetractHeight2 += height; RetractSpeed2 = speed; } if (Movements.Count == 1) // Only 1 move, this is the PositionZ only { LiftSpeed = Movements[0].Speed; return; } // Sanitize if (PositionZ.HasValue && LiftHeight.HasValue && !IsExposed) // Lift before exposure order, need to remove layer height as offset { var liftHeight = Layer.RoundHeight(LiftHeight.Value - (PositionZ.Value - PreviousPositionZ)); if(liftHeight <= 0) return; // Something not right or not the correct moment, skip LiftHeight = liftHeight; } if (RetractHeight2.HasValue) // Need to fix the propose of this value { RetractHeight2 = Layer.RoundHeight(LiftHeightTotal - RetractHeight2.Value); (RetractSpeed, RetractSpeed2) = (RetractSpeed2, RetractSpeed); } if (LiftHeight.HasValue && RetractHeight2.HasValue) // Sanitize RetractHeight2 value { RetractHeight2 = Math.Clamp(RetractHeight2.Value, 0, LiftHeightTotal); } } /// /// Set gathered data to the layer /// public void SetLayer(bool reinit = false) { if (!IsValid) return; uint layerIndex = LayerIndex.Value; var layer = SlicerFile[layerIndex]; PositionZ ??= PreviousPositionZ; layer.PositionZ = PositionZ.Value; layer.WaitTimeBeforeCure = WaitTimeBeforeCure ?? 0; layer.ExposureTime = ExposureTime ?? SlicerFile.GetBottomOrNormalValue(layer, SlicerFile.BottomExposureTime, SlicerFile.ExposureTime); layer.WaitTimeAfterCure = WaitTimeAfterCure ?? 0; layer.LiftHeight = LiftHeight ?? 0; layer.LiftSpeed = LiftSpeed ?? SlicerFile.GetBottomOrNormalValue(layer, SlicerFile.BottomLiftSpeed, SlicerFile.LiftSpeed); layer.LiftHeight2 = LiftHeight2 ?? 0; layer.LiftSpeed2 = LiftSpeed2 ?? SlicerFile.GetBottomOrNormalValue(layer, SlicerFile.BottomLiftSpeed2, SlicerFile.LiftSpeed2); layer.WaitTimeAfterLift = WaitTimeAfterLift ?? 0; layer.RetractSpeed = RetractSpeed ?? SlicerFile.GetBottomOrNormalValue(layer, SlicerFile.BottomRetractSpeed, SlicerFile.RetractSpeed); layer.RetractHeight2 = RetractHeight2 ?? 0; layer.RetractSpeed2 = RetractSpeed2 ?? SlicerFile.GetBottomOrNormalValue(layer, SlicerFile.BottomRetractSpeed2, SlicerFile.RetractSpeed2); layer.LightPWM = LightPWM ?? 0;//SlicerFile.GetInitialLayerValueOrNormal(layerIndex, SlicerFile.BottomLightPWM, SlicerFile.LightPWM); if (SlicerFile.GCode.SyncMovementsWithDelay) // Dirty fix of the value { var syncTime = OperationCalculator.LightOffDelayC.CalculateSeconds(layer, 1.5f); if (syncTime < layer.WaitTimeBeforeCure) { layer.WaitTimeBeforeCure = (float) Math.Round(layer.WaitTimeBeforeCure - syncTime, 2); } } if(reinit) Init(); } } }