/* * 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 System.ComponentModel; using System.Data; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Layers; using UVtools.Core.Objects; using UVtools.Core.Operations; namespace UVtools.Core.GCode { public class GCodeBuilder : BindableBase { #region Commands public GCodeCommand CommandUnitsMillimetersG21 { get; } = new("G21", null, "Set units to be mm"); public GCodeCommand CommandPositioningAbsoluteG90 { get; } = new("G90", null, "Absolute positioning"); public GCodeCommand CommandPositioningPartialG91 { get; } = new("G91", null, "Partial positioning"); public GCodeCommand CommandMotorsOnM17 { get; } = new("M17", null, "Enable motors"); public GCodeCommand CommandMotorsOffM18 { get; } = new("M18", null, "Disable motors"); public GCodeCommand CommandHomeG28 { get; } = new("G28", "Z0", "Home Z"); public GCodeCommand CommandMoveG0 { get; } = new("G0", "Z{0} F{1}", "Move Z"); public GCodeCommand CommandMoveG1 { get; } = new("G1", "Z{0} F{1}", "Move Z"); public GCodeCommand CommandWaitG4 { get; } = new("G4", "P{0}", "Delay"); public GCodeCommand CommandShowImageM6054 = new("M6054", "\"{0}\"", "Show image"); public GCodeCommand CommandClearImage = new("; Blank"); // Clear image public GCodeCommand CommandTurnLEDM106 { get; } = new("M106", "S{0}", "Turn LED"); #endregion #region Enums public enum GCodePositioningTypes : byte { Absolute, Partial } public enum GCodeTimeUnits : byte { /// /// ms /// Milliseconds, /// /// s /// Seconds } public enum GCodeSpeedUnits : byte { /// /// mm/s /// MillimetersPerSecond, /// /// mm/m /// MillimetersPerMinute, /// /// cm/m /// CentimetersPerMinute, } public enum GCodeMoveCommands : byte { G0, // Fast G1 // Interpolated } public enum GCodeShowImageTypes : byte { FilenamePng0Started, FilenamePng1Started, LayerIndex0Started, LayerIndex1Started, } #endregion #region Members private readonly StringBuilder _gcode = new(); private GCodePositioningTypes _gCodePositioningType = GCodePositioningTypes.Absolute; private GCodeTimeUnits _gCodeTimeUnit = GCodeTimeUnits.Milliseconds; private GCodeSpeedUnits _gCodeSpeedUnit = GCodeSpeedUnits.MillimetersPerMinute; private GCodeShowImageTypes _gCodeShowImageType = GCodeShowImageTypes.FilenamePng1Started; private bool _syncMovementsWithDelay; private bool _useTailComma = true; private bool _useComments = true; private ushort _maxLedPower = byte.MaxValue; private uint _lineCount; private GCodeMoveCommands _layerMoveCommand; private GCodeMoveCommands _endGCodeMoveCommand; #endregion #region Properties public GCodePositioningTypes GCodePositioningType { get => _gCodePositioningType; set => RaiseAndSetIfChanged(ref _gCodePositioningType, value); } public GCodeTimeUnits GCodeTimeUnit { get => _gCodeTimeUnit; set => RaiseAndSetIfChanged(ref _gCodeTimeUnit, value); } public GCodeSpeedUnits GCodeSpeedUnit { get => _gCodeSpeedUnit; set => RaiseAndSetIfChanged(ref _gCodeSpeedUnit, value); } public GCodeShowImageTypes GCodeShowImageType { get => _gCodeShowImageType; set => RaiseAndSetIfChanged(ref _gCodeShowImageType, value); } public GCodeMoveCommands LayerMoveCommand { get => _layerMoveCommand; set => RaiseAndSetIfChanged(ref _layerMoveCommand, value); } public GCodeMoveCommands EndGCodeMoveCommand { get => _endGCodeMoveCommand; set => RaiseAndSetIfChanged(ref _endGCodeMoveCommand, value); } public bool SyncMovementsWithDelay { get => _syncMovementsWithDelay; set => RaiseAndSetIfChanged(ref _syncMovementsWithDelay, value); } public bool UseTailComma { get => _useTailComma; set => RaiseAndSetIfChanged(ref _useTailComma, value); } public bool UseComments { get => _useComments; set => RaiseAndSetIfChanged(ref _useComments, value); } public ushort MaxLEDPower { get => _maxLedPower; set => RaiseAndSetIfChanged(ref _maxLedPower, value); } public string BeginStartGCodeComments { get; set; } = ";START_GCODE_BEGIN"; public string EndStartGCodeComments { get; set; } = ";END_GCODE_BEGIN"; public string BeginLayerComments { get; set; } = ";LAYER_START:{0}" + Environment.NewLine + ";PositionZ:{1}mm"; public string EndLayerComments { get; set; } = ";LAYER_END"; public string BeginEndGCodeComments { get; set; } = ";START_GCODE_END"; public string EndEndGCodeComments { get; set; } = ";END_GCODE_END" + Environment.NewLine + ";"; public uint LineCount { get => _lineCount; set { if(!RaiseAndSetIfChanged(ref _lineCount, value)) return; //RaisePropertyChanged(nameof(IsEmpty)); } } public bool IsEmpty => _lineCount <= 0 || _gcode.Length <= 0; public int Length => _gcode.Length; #endregion #region StringBuilder public void Append(string text) { _gcode.Append(text); } public void Append(StringBuilder sb) { _gcode.Append(sb); } public void AppendLine() { _gcode.AppendLine(); //LineCount++; } public void AppendLine(string line) { if (line is null) return; _gcode.AppendLine(line); LineCount++; } public void AppendLine(string line, params object[] args) { if (line is null) return; _gcode.AppendLine(string.Format(line, args)); LineCount++; } public void AppendLine(GCodeCommand command) { if (!command.Enabled) return; AppendLine(command.ToString(_useComments, _useTailComma)); LineCount++; } public void AppendLineOverrideComment(GCodeCommand command, string comment, params object[] args) { if (!command.Enabled) return; AppendLine(command.ToStringOverrideComment(_useComments, _useTailComma, comment, args)); LineCount++; } public void AppendLine(GCodeCommand command, params object[] args) { if (!command.Enabled) return; AppendLine(command.ToString(_useComments, _useTailComma, args)); LineCount++; } public void AppendFormat(string format, params object[] args) { _gcode.AppendFormat(format, args); LineCount += (uint)format.Count(c => c == '\n'); } public void AppendLineIfCanComment(string line) { if (string.IsNullOrWhiteSpace(line) || !_useComments) return; AppendLine(line); } public void AppendLineIfCanComment(string line, params object[] args) { if (string.IsNullOrWhiteSpace(line) || !_useComments) return; AppendLine(line, args); } public void AppendComment(string comment) { if (string.IsNullOrWhiteSpace(comment) || !_useComments) return; AppendLine($";{comment}"); } public void Clear() { _gcode.Clear(); LineCount = 0; } public override string ToString() => _gcode.ToString(); #endregion #region Methods public string FormatGCodeLine(string line, string comment = null) { if (line[0] == ';') return line; if (_useComments && !string.IsNullOrWhiteSpace(comment)) { line += $";{comment}"; } else if (_useTailComma) { line += ';'; } return line; } public void AppendUVtools() { AppendComment($"Generated by {About.Software} v{About.VersionStr} {About.Arch} @ {DateTime.UtcNow}"); } public void AppendStartGCode() { AppendLineIfCanComment(BeginStartGCodeComments); AppendUnitsMmG21(); AppendPositioningType(); AppendLightOffM106(); AppendMotorsOn(); AppendClearImage(); AppendHomeZG28(); AppendLineIfCanComment(EndStartGCodeComments); AppendLine(); } public void AppendEndGCode(float raiseZ = 0, float feedRate = 0) { AppendLineIfCanComment(BeginEndGCodeComments); AppendLightOffM106(); if (raiseZ > 0) { if (_endGCodeMoveCommand == GCodeMoveCommands.G0) AppendMoveG0(raiseZ, feedRate); else AppendMoveG1(raiseZ, feedRate); } AppendMotorsOff(); AppendLineIfCanComment(EndEndGCodeComments); } public void AppendUnitsMmG21() { AppendLine(CommandUnitsMillimetersG21); } public void AppendPositioningType() { switch (GCodePositioningType) { case GCodePositioningTypes.Absolute: AppendLine(CommandPositioningAbsoluteG90); break; case GCodePositioningTypes.Partial: AppendLine(CommandPositioningPartialG91); break; default: throw new ArgumentOutOfRangeException(); } } public void AppendMotorsOn() { AppendLine(CommandMotorsOnM17); } public void AppendMotorsOff() { AppendLine(CommandMotorsOffM18); } public void AppendTurnMotors(bool enable) { if (enable) AppendMotorsOn(); else AppendMotorsOff(); } public void AppendHomeZG28() { AppendLine(CommandHomeG28); } public void AppendMoveGx(float z, float feedRate) { if(_layerMoveCommand == GCodeMoveCommands.G0) AppendMoveG0(z, feedRate); else AppendMoveG1(z, feedRate); } public void AppendLiftMoveGx(List<(float z, float feedrate)> lifts, List<(float z, float feedrate)> retracts, float waitAfterLift = 0, float waitAfterRetract = 0, Layer layer = null) { if (_layerMoveCommand == GCodeMoveCommands.G0) AppendLiftMoveG0(lifts, retracts, waitAfterLift, waitAfterRetract, layer); else AppendLiftMoveG1(lifts, retracts, waitAfterLift, waitAfterRetract, layer); } public void AppendMoveG0(float z, float feedRate) { if(z == 0 || feedRate <= 0) return; AppendLine(CommandMoveG0, z, feedRate); } public void AppendLiftMoveG0(List<(float z, float feedrate)> lifts, List<(float z, float feedrate)> retracts, float waitAfterLift = 0, float waitAfterRetract = 0, Layer layer = null) { if (lifts.Count == 0 || lifts.All(tuple => tuple.z <= 0)) return; for (var i = 0; i < lifts.Count; i++) { if (lifts[i].z <= 0) continue; AppendLineOverrideComment(CommandMoveG0, $"Z Lift ({i+1})", lifts[i].z, lifts[i].feedrate); // Z Lift } if (_syncMovementsWithDelay && layer is not null) { var seconds = OperationCalculator.LightOffDelayC.CalculateSecondsLiftOnly(layer, 0.75f); var time = ConvertFromSeconds(seconds); AppendWaitG4($"0{time}", "Sync movement"); } if (waitAfterLift > 0) { AppendWaitG4(waitAfterLift, "Wait after lift"); } if (retracts.Count > 0 || retracts.All(tuple => tuple.z != 0)) { for (var i = 0; i < retracts.Count; i++) { if (retracts[i].z == 0) continue; AppendLineOverrideComment(CommandMoveG0, $"Retract ({i+1})", retracts[i].z, retracts[i].feedrate); } if (_syncMovementsWithDelay && layer is not null) { var seconds = OperationCalculator.LightOffDelayC.CalculateSecondsLiftOnly(layer.RetractHeight, layer.RetractSpeed, layer.RetractHeight2, layer.RetractSpeed2, 0.75f); var time = ConvertFromSeconds(seconds); AppendWaitG4($"0{time}", "Sync movement"); } } if (waitAfterRetract > 0) { AppendWaitG4(waitAfterRetract, "Wait after retract"); } } public void AppendLiftMoveG0(float upZ, float upFeedrate, float downZ, float downFeedrate, float waitAfterLift = 0, float waitAfterRetract = 0, Layer layer = null) => AppendLiftMoveG0( new List<(float, float)> { new(upZ, upFeedrate) }, new List<(float, float)> { new(downZ, downFeedrate) }, waitAfterLift, waitAfterRetract, layer); public void AppendMoveG1(float z, float feedRate) { if (z == 0 || feedRate <= 0) return; AppendLine(CommandMoveG1, z, feedRate); } public void AppendLiftMoveG1(List<(float z, float feedrate)> lifts, List<(float z, float feedrate)> retracts, float waitAfterLift = 0, float waitAfterRetract = 0, Layer layer = null) { if (lifts.Count == 0 || lifts.All(tuple => tuple.z <= 0)) return; for (var i = 0; i < lifts.Count; i++) { if (lifts[i].z <= 0) continue; AppendLineOverrideComment(CommandMoveG1, $"Z Lift ({i+1})", lifts[i].z, lifts[i].feedrate); // Z Lift } if (_syncMovementsWithDelay && layer is not null) { var seconds = OperationCalculator.LightOffDelayC.CalculateSecondsLiftOnly(layer, 0.75f); var time = ConvertFromSeconds(seconds); AppendWaitG4($"0{time}", "Sync movement"); } if (waitAfterLift > 0) { AppendWaitG4(waitAfterLift, "Wait after lift"); } if (retracts.Count > 0 || retracts.All(tuple => tuple.z != 0)) { for (var i = 0; i < retracts.Count; i++) { if (retracts[i].z == 0) continue; AppendLineOverrideComment(CommandMoveG1, $"Retract ({i+1})", retracts[i].z, retracts[i].feedrate); } if (_syncMovementsWithDelay && layer is not null) { var seconds = OperationCalculator.LightOffDelayC.CalculateSecondsLiftOnly(layer.RetractHeight, layer.RetractSpeed, layer.RetractHeight2, layer.RetractSpeed2, 0.75f); var time = ConvertFromSeconds(seconds); AppendWaitG4($"0{time}", "Sync movement"); } } if (waitAfterRetract > 0) { AppendWaitG4(waitAfterRetract, "Wait after retract"); } } public void AppendLiftMoveG1(float upZ, float upFeedrate, float downZ, float downFeedrate, float waitAfterLift = 0, float waitAfterRetract = 0, Layer layer = null) => AppendLiftMoveG0( new List<(float, float)> { new(upZ, upFeedrate) }, new List<(float, float)> { new(downZ, downFeedrate) }, waitAfterLift, waitAfterRetract, layer); public void AppendWaitG4(float time, string comment = null) { if (time < 0) return; AppendLineOverrideComment(CommandWaitG4, comment, time); } public void AppendWaitG4(string timeStr, string comment = null) { if (!float.TryParse(timeStr, out var time)) return; if (time < 0) return; AppendLineOverrideComment(CommandWaitG4, comment, timeStr); } public void AppendTurnLightM106(ushort value) { AppendLineOverrideComment(CommandTurnLEDM106, "Turn LED " + (value == 0 ? "OFF" : "ON"), value); } public void AppendLightOffM106() => AppendTurnLightM106(0); public void AppendLightFullM106() => AppendTurnLightM106(_maxLedPower); public void AppendClearImage() { AppendLine(CommandClearImage); } public void AppendExposure(float time, ushort pwmValue = 255) { if (pwmValue <= 0 || time <= 0) return; AppendTurnLightM106(pwmValue); AppendWaitG4(time, "Cure time/delay"); AppendLightOffM106(); AppendClearImage(); } public void AppendShowImageM6054(string filename) { AppendLine(CommandShowImageM6054, filename); } public void AppendShowImageM6054(uint layerIndex) { AppendLine(CommandShowImageM6054, layerIndex); } public string GetShowImageString(uint layerIndex) => _gCodeShowImageType switch { GCodeShowImageTypes.FilenamePng0Started => $"{layerIndex}.png", GCodeShowImageTypes.FilenamePng1Started => $"{layerIndex + 1}.png", GCodeShowImageTypes.LayerIndex0Started => $"{layerIndex}", GCodeShowImageTypes.LayerIndex1Started => $"{layerIndex + 1}", _ => throw new InvalidExpressionException($"Unhandled image type for {_gCodeShowImageType}") }; public string GetShowImageString(string value) => _gCodeShowImageType switch { GCodeShowImageTypes.FilenamePng0Started => $"{value}.png", GCodeShowImageTypes.FilenamePng1Started => $"{value}.png", GCodeShowImageTypes.LayerIndex0Started => $"{value}", GCodeShowImageTypes.LayerIndex1Started => $"{value}", _ => throw new InvalidExpressionException($"Unhandled image type for {_gCodeShowImageType}") }; public void RebuildGCode(FileFormat slicerFile, StringBuilder header) => RebuildGCode(slicerFile, header?.ToString()); public void RebuildGCode(FileFormat slicerFile, string header = null) { Clear(); AppendUVtools(); if (slicerFile.LayerCount == 0) return; if (!string.IsNullOrWhiteSpace(header)) { Append(header); AppendLine(); } AppendStartGCode(); float lastZPosition = 0; // Defaults for: Absolute, mm/min and s for (uint layerIndex = 0; layerIndex < slicerFile.LayerCount; layerIndex++) { var layer = slicerFile[layerIndex]; float waitBeforeCure = ConvertFromSeconds(layer.WaitTimeBeforeCure); float exposureTime = ConvertFromSeconds(layer.ExposureTime); float waitAfterCure = ConvertFromSeconds(layer.WaitTimeAfterCure); float liftHeight = layer.LiftHeight; //float liftZPos = Layer.RoundHeight(liftHeight + layer.PositionZ); //float liftZPosAbs = liftZPos; float liftSpeed = ConvertFromMillimetersPerMinute(layer.LiftSpeed); float liftHeight2 = layer.LiftHeight2; float liftSpeed2 = ConvertFromMillimetersPerMinute(layer.LiftSpeed2); float waitAfterLift = ConvertFromSeconds(layer.WaitTimeAfterLift); float retractHeight = layer.RetractHeight; float retractSpeed = ConvertFromMillimetersPerMinute(layer.RetractSpeed); float retractHeight2 = layer.RetractHeight2; float retractSpeed2 = ConvertFromMillimetersPerMinute(layer.RetractSpeed2); float liftHeightTotal = layer.LiftHeightTotal; ushort pwmValue = layer.LightPWM; if (_maxLedPower != byte.MaxValue) { pwmValue = (ushort)(_maxLedPower * pwmValue / byte.MaxValue); } var lifts = new List<(float z, float feedrate)>(); var retracts = new List<(float z, float feedrate)>(); switch (GCodePositioningType) { case GCodePositioningTypes.Absolute: var absLiftPos = Layer.RoundHeight(liftHeight + layer.PositionZ); var absLiftPos2 = Layer.RoundHeight(absLiftPos + liftHeight2); var absRetractPos = Layer.RoundHeight(absLiftPos2 - retractHeight); if (liftHeight > 0) lifts.Add((absLiftPos, liftSpeed)); if (liftHeight2 > 0) lifts.Add((absLiftPos2, liftSpeed2)); if (retractHeight > 0 && absRetractPos >= layer.PositionZ) retracts.Add((absRetractPos, retractSpeed)); if (retractHeight2 > 0 && absRetractPos > layer.PositionZ) retracts.Add((layer.PositionZ, retractSpeed2)); break; case GCodePositioningTypes.Partial: var partialLiftPos = Layer.RoundHeight(layer.PositionZ - lastZPosition + liftHeight); if (liftHeight > 0) { lifts.Add((partialLiftPos, liftSpeed)); if (liftHeight2 > 0) lifts.Add((liftHeight2, liftSpeed2)); } else { if (liftHeight2 > 0) lifts.Add((Layer.RoundHeight(partialLiftPos + liftHeight2), liftSpeed2)); } if (Layer.RoundHeight(retractHeight + retractHeight2) != liftHeightTotal) // Fail-safe { retracts.Add((-liftHeightTotal, retractSpeed)); } else { if (retractHeight > 0) retracts.Add((-retractHeight, retractSpeed)); if (retractHeight2 > 0) retracts.Add((-retractHeight2, retractSpeed2)); } break; } AppendLineIfCanComment(BeginLayerComments, layerIndex, layer.PositionZ); //if (layer.CanExpose) //{ Dont check this for compability AppendShowImageM6054(GetShowImageString(layerIndex)); //} if (liftHeightTotal > 0 && Layer.RoundHeight(liftHeightTotal + layer.PositionZ) > layer.PositionZ) { AppendLiftMoveGx(lifts, retracts, waitAfterLift, 0, layer); } else if (lastZPosition < layer.PositionZ) // Ensure Z is on correct position { switch (GCodePositioningType) { case GCodePositioningTypes.Absolute: AppendMoveGx(layer.PositionZ, liftSpeed); break; case GCodePositioningTypes.Partial: AppendMoveGx(Layer.RoundHeight(layer.PositionZ - lastZPosition), liftSpeed); break; } } AppendWaitG4(waitBeforeCure, "Wait before cure"); // Safer to parse if present AppendExposure(exposureTime, pwmValue); if(waitAfterCure > 0) AppendWaitG4(waitAfterCure, "Wait after cure"); AppendLineIfCanComment(EndLayerComments, layerIndex, layer.PositionZ); AppendLine(); lastZPosition = layer.PositionZ; } float finalRaiseZPosition = Math.Max(lastZPosition, slicerFile.MachineZ); switch (GCodePositioningType) { case GCodePositioningTypes.Partial: finalRaiseZPosition = Layer.RoundHeight(finalRaiseZPosition - lastZPosition); break; } AppendEndGCode(finalRaiseZPosition, ConvertFromMillimetersPerMinute(slicerFile.RetractSpeed)); } public void RebuildGCode(FileFormat slicerFile, object[] configs, string separator = ":") { StringBuilder sb = null; if (configs is not null) { sb = new StringBuilder(); foreach (var config in configs) { foreach (var propertyInfo in config.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); string name; if (displayNameAttribute is null) { name = propertyInfo.Name; if(name == "Item") continue; } else { name = displayNameAttribute.DisplayName; } sb.AppendLine($";{name}{separator}{propertyInfo.GetValue(config)}"); } } } RebuildGCode(slicerFile, sb); } public GCodePositioningTypes ParsePositioningTypeFromGCode(string gcode) { using var reader = new StringReader(gcode); string line; while ((line = reader.ReadLine()) != null) { if (line.StartsWith(CommandPositioningAbsoluteG90.Command)) return GCodePositioningTypes.Absolute; if (line.StartsWith(CommandPositioningPartialG91.Command)) return GCodePositioningTypes.Partial; } return _gCodePositioningType; } public void ParseLayersFromGCode(FileFormat slicerFile, bool rebuildGlobalTable = true) => ParseLayersFromGCode(slicerFile, null, rebuildGlobalTable); public void ParseLayersFromGCode(FileFormat slicerFile, string gcode, bool rebuildGlobalTable = true) { if (slicerFile.LayerCount == 0) return; if (string.IsNullOrWhiteSpace(gcode)) { gcode = _gcode.ToString(); if (string.IsNullOrWhiteSpace(gcode)) return; } var positionType = GCodePositioningTypes.Absolute; // test /*gcode = "G90\r\n" + "M6054 \"1.png\";\r\n" + "G4 P0;\r\n" + "M106 S300;\r\n" + "G4 P36000;\r\n" + "M106 S0;\r\n" + "G4 P5000;\r\n" + "\r\n" ;*/ var layerBlock = new GCodeLayer(slicerFile); using var reader = new StringReader(gcode); string line; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (string.IsNullOrWhiteSpace(line)) continue; // Search for and switch position type when needed if (line.StartsWith(CommandPositioningAbsoluteG90.Command)) { positionType = GCodePositioningTypes.Absolute; continue; } if (line.StartsWith(CommandPositioningPartialG91.Command)) { positionType = GCodePositioningTypes.Partial; continue; } Match match = null; // Display image if (line.StartsWith(CommandShowImageM6054.Command)) { match = Regex.Match(line, CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(@"(\d+)")), RegexOptions.IgnoreCase); if (match.Success && match.Groups.Count >= 2) // Begin new layer { var layerIndex = uint.Parse(match.Groups[1].Value); if (_gCodeShowImageType is GCodeShowImageTypes.FilenamePng1Started or GCodeShowImageTypes .LayerIndex1Started) layerIndex--; if (layerIndex > slicerFile.LayerCount) { throw new FileLoadException( $"GCode parser detected the layer {layerIndex}, but the file was sliced to {slicerFile.LayerCount} layers.", slicerFile.FileFullPath); } // Propagate values before switch to the new layer layerBlock.SetLayer(true); layerBlock.LayerIndex = layerIndex; continue; } } if (!layerBlock.IsValid) continue; // No layer yet found to work on, skip // Check moves if (line.StartsWith(CommandMoveG0.Command) || line.StartsWith(CommandMoveG1.Command)) { var moveG0Regex = Regex.Match(line, CommandMoveG0.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(([0-9]*[.])?[0-9]+)"), RegexOptions.IgnoreCase); var moveG1Regex = Regex.Match(line, CommandMoveG1.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(([0-9]*[.])?[0-9]+)"), RegexOptions.IgnoreCase); match = moveG0Regex.Success && moveG0Regex.Groups.Count >= 2 ? moveG0Regex : moveG1Regex; if (match.Success && match.Groups.Count >= 4 && !layerBlock.RetractSpeed2.HasValue && layerBlock.LightOffCount < 2) { float pos = float.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); float speed = ConvertToMillimetersPerMinute(float.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture)); layerBlock.Movements.Add(new(pos, speed)); layerBlock.AssignMovements(positionType); /*if (!layerBlock.PositionZ.HasValue) // Lift or pos, set here for now { switch (positionType) { case GCodePositioningTypes.Absolute: layerBlock.PositionZ = pos; break; case GCodePositioningTypes.Partial: layerBlock.PositionZ = Layer.RoundHeight(positionZ + pos); break; } layerBlock.LiftSpeed = speed; continue; } // ~90% sure its retract here, still is possible to bug this with 2 lifts layerBlock.RetractSpeed = speed; switch (positionType) { case GCodePositioningTypes.Absolute: layerBlock.LiftHeight = Layer.RoundHeight(layerBlock.PositionZ.Value - pos); layerBlock.PositionZ = positionZ = pos; break; case GCodePositioningTypes.Partial: layerBlock.LiftHeight = layerBlock.PositionZ - positionZ; layerBlock.PositionZ = positionZ = Layer.RoundHeight(layerBlock.PositionZ.Value + pos); break; }*/ continue; } } // Check for waits if (line.StartsWith(CommandWaitG4.Command)) { match = Regex.Match(line, CommandWaitG4.ToStringWithoutComments(@"(([0-9]*[.])?[0-9]+)"), RegexOptions.IgnoreCase); if (match.Success && match.Groups.Count >= 2) { if (_syncMovementsWithDelay && match.Groups[1].Value.StartsWith('0')) continue; // Sync movement delay, skip var waitTime = float.Parse(match.Groups[1].Value); if (layerBlock.PositionZ.HasValue && !layerBlock.RetractSpeed.HasValue) // Must be wait time after lift, if not, don't blame me! { layerBlock.WaitTimeAfterLift ??= 0; layerBlock.WaitTimeAfterLift += ConvertToSeconds(waitTime); continue; } if (!layerBlock.LightPWM.HasValue) // Before cure { layerBlock.WaitTimeBeforeCure ??= 0; layerBlock.WaitTimeBeforeCure += ConvertToSeconds(waitTime); continue; } if (layerBlock.IsExposing) // Must be exposure time, if not, don't blame me! { layerBlock.ExposureTime ??= 0; layerBlock.ExposureTime += ConvertToSeconds(waitTime); continue; } if (layerBlock.IsAfterLightOff) { if (!layerBlock.WaitTimeBeforeCure .HasValue) // Novamaker fix, delay on last line, broke logic but safer { layerBlock.WaitTimeBeforeCure ??= 0; layerBlock.WaitTimeBeforeCure += ConvertToSeconds(waitTime); } else { layerBlock.WaitTimeAfterCure ??= 0; layerBlock.WaitTimeAfterCure += ConvertToSeconds(waitTime); } continue; } continue; } } // Check LightPWM if (line.StartsWith(CommandTurnLEDM106.Command)) { match = Regex.Match(line, CommandTurnLEDM106.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase); if (match.Success && match.Groups.Count >= 2) { byte pwm; if (_maxLedPower == byte.MaxValue) { pwm = byte.Parse(match.Groups[1].Value); } else { ushort pwmValue = ushort.Parse(match.Groups[1].Value); pwm = (byte) (pwmValue * byte.MaxValue / _maxLedPower); } if (pwm == 0 && layerBlock.LightPWM.HasValue) { layerBlock.LightOffCount++; } else if (!layerBlock.IsAfterLightOff) { layerBlock.LightPWM = pwm; } continue; } } } // Propagate values of left over layer layerBlock.SetLayer(); /*for (uint layerIndex = 0; layerIndex < slicerFile.LayerCount; layerIndex++) { var layer = slicerFile[layerIndex]; if(layer is null) continue; var startStr = CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(layerIndex)); var endStr = CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(layerIndex+1)); gcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length + 1); var endStrIndex = gcode.IndexOf(endStr, StringComparison.Ordinal); var stripGcode = endStrIndex > 0 ? gcode[..endStrIndex] : gcode;//.Trim(' ', '\n', '\r', '\t'); float liftHeight = 0;// this allow read back no lifts slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLiftHeight, slicerFile.LiftHeight); float liftSpeed = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLiftSpeed, slicerFile.LiftSpeed); float retractSpeed = slicerFile.RetractSpeed; float lightOffDelay = 0; byte pwm = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLightPWM, slicerFile.LightPWM); float exposureTime = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomExposureTime, slicerFile.ExposureTime); var moveG0Regex = Regex.Matches(stripGcode, CommandMoveG0.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(\d+)"), RegexOptions.IgnoreCase); var moveG1Regex = Regex.Matches(stripGcode, CommandMoveG1.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(\d+)"), RegexOptions.IgnoreCase); var waitG4Regex = Regex.Matches(stripGcode, CommandWaitG4.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase); var pwmM106Regex = Regex.Match(stripGcode, CommandTurnLEDM106.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase); var moveRegex = moveG0Regex.Count > 0 ? moveG0Regex : moveG1Regex; if (moveRegex.Count >= 1 && moveRegex[0].Success) { float liftPosTemp = float.Parse(moveRegex[0].Groups[1].Value, CultureInfo.InvariantCulture); float liftSpeedTemp = ConvertToMillimetersPerMinute(float.Parse(moveRegex[0].Groups[3].Value, CultureInfo.InvariantCulture)); if (moveRegex.Count >= 2 && moveRegex[1].Success) { float retractPos = float.Parse(moveRegex[1].Groups[1].Value, CultureInfo.InvariantCulture); retractSpeed = ConvertToMillimetersPerMinute(float.Parse(moveRegex[1].Groups[3].Value, CultureInfo.InvariantCulture)); liftSpeed = liftSpeedTemp; switch (positionType) { case GCodePositioningTypes.Absolute: liftHeight = Layer.RoundHeight(liftPosTemp - retractPos); positionZ = retractPos; break; case GCodePositioningTypes.Partial: liftHeight = liftPosTemp; positionZ = Layer.RoundHeight(positionZ + liftPosTemp + retractPos); break; } } else { if (liftPosTemp - positionZ <= FileFormat.MaximumLayerHeight) { switch (positionType) { case GCodePositioningTypes.Absolute: positionZ = liftPosTemp; break; case GCodePositioningTypes.Partial: positionZ = Layer.RoundHeight(positionZ + liftPosTemp); break; } } } } if (pwmM106Regex.Success) { if (_maxLedPower == byte.MaxValue) { pwm = byte.Parse(pwmM106Regex.Groups[1].Value); } else { ushort pwmValue = ushort.Parse(pwmM106Regex.Groups[1].Value); pwm = (byte)(pwmValue * byte.MaxValue / _maxLedPower); } } if (waitG4Regex.Count >= 1 && waitG4Regex[0].Success) { lightOffDelay = ConvertToSeconds(float.Parse(waitG4Regex[0].Groups[1].Value, CultureInfo.InvariantCulture)); if (waitG4Regex.Count >= 2 && waitG4Regex[1].Success) { exposureTime = ConvertToSeconds(float.Parse(waitG4Regex[1].Groups[1].Value, CultureInfo.InvariantCulture)); } else // Only one match, meaning light off delay is not present and the only time is the cure time { exposureTime = lightOffDelay; lightOffDelay = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLightOffDelay, slicerFile.LightOffDelay); } } layer.PositionZ = positionZ; layer.ExposureTime = exposureTime; layer.LiftHeight = liftHeight; layer.LiftSpeed = liftSpeed; layer.RetractSpeed = retractSpeed; layer.LightOffDelay = lightOffDelay; layer.LightPWM = pwm; } */ if (rebuildGlobalTable) { slicerFile.UpdateGlobalPropertiesFromLayers(); } } public StringReader GetStringReader() { return new(_gcode.ToString()); } /// /// Converts seconds to current gcode norm /// /// /// public float ConvertFromSeconds(float seconds) { return _gCodeTimeUnit switch { GCodeTimeUnits.Seconds => seconds, GCodeTimeUnits.Milliseconds => TimeExtensions.SecondsToMilliseconds(seconds), _ => throw new InvalidExpressionException($"Unhandled time unit for {_gCodeTimeUnit}") }; } /// /// Converts speed in mm/min to current gcode norm /// /// Millimeters per minute /// public float ConvertFromMillimetersPerMinute(float mmMin) { return _gCodeSpeedUnit switch { GCodeSpeedUnits.MillimetersPerMinute => mmMin, GCodeSpeedUnits.MillimetersPerSecond => (float) Math.Round(mmMin / 60, 2), GCodeSpeedUnits.CentimetersPerMinute => (float) Math.Round(mmMin / 10, 2), _ => throw new InvalidExpressionException($"Unhandled speed unit for {_gCodeSpeedUnit}") }; } /// /// Converts time from current gcode norm in to s /// /// Time in /// public float ConvertToSeconds(float time) { return _gCodeTimeUnit switch { GCodeTimeUnits.Seconds => time, GCodeTimeUnits.Milliseconds => TimeExtensions.MillisecondsToSeconds(time), _ => throw new InvalidExpressionException($"Unhandled time unit for {_gCodeTimeUnit}") }; } /// /// Converts speed from current gcode norm in to mm/min /// /// Speed in /// public float ConvertToMillimetersPerMinute(float speed) { return _gCodeSpeedUnit switch { GCodeSpeedUnits.MillimetersPerMinute => speed, GCodeSpeedUnits.MillimetersPerSecond => (float)Math.Round(speed * 60, 2), GCodeSpeedUnits.CentimetersPerMinute => (float)Math.Round(speed * 10, 2), _ => throw new InvalidExpressionException($"Unhandled speed unit for {_gCodeSpeedUnit}") }; } } #endregion }