/// <summary> /// Parse codes from the given input string asynchronously /// </summary> /// <returns>Parsed G/M/T-codes</returns> public async IAsyncEnumerable <Code> ParseAsync() { using MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(Code)); using StreamReader reader = new StreamReader(stream); CodeParserBuffer buffer = new CodeParserBuffer((int)stream.Length, Code.Contains('\n')); while (buffer.GetPosition(reader) < stream.Length) { Code code = new Code() { Channel = Channel, Connection = Connection }; if (await DuetAPI.Commands.Code.ParseAsync(reader, code, buffer)) { yield return(code); } } }
/// <summary> /// Try to extract thumbnails from a given file /// </summary> /// <param name="reader">Stream reader to read from</param> /// <param name="codeParserBuffer">Read buffer</param> /// <param name="parsedFileInfo">File information</param> /// <param name="code">Code instance to reuse</param> /// <returns>Asynchronous task</returns> public static async Task ProcessAsync(StreamReader reader, CodeParserBuffer codeParserBuffer, ParsedFileInfo parsedFileInfo, Code code) { _logger.Info($"Processing Image {parsedFileInfo.FileName}"); StringBuilder imageBuffer = new(); code.Reset(); //Keep reading the data from the file while (codeParserBuffer.GetPosition(reader) < reader.BaseStream.Length) { Program.CancellationToken.ThrowIfCancellationRequested(); if (!await DuetAPI.Commands.Code.ParseAsync(reader, code, codeParserBuffer)) { continue; } //Icon data goes until the first line of executable code. if (code.Type == CodeType.Comment) { imageBuffer.Append(code.Comment.Trim()); code.Reset(); } else { try { ParsedThumbnail thumbnail = ReadImage(imageBuffer.ToString()); parsedFileInfo.Thumbnails.Add(thumbnail); _logger.Error("Icon Thumbnails Found"); } catch { //throw it away } return; } } }
/// <summary> /// Extract thumbnails generated by Prusa Slicer from a file /// </summary> /// <param name="reader">Stream reader to read from</param> /// <param name="codeParserBuffer">Parser buffer</param> /// <param name="parsedFileInfo">File information</param> /// <param name="code">Code to reuse while parsing</param> /// <returns>Asynchronous task</returns> public static async Task ProcessAsync(StreamReader reader, CodeParserBuffer codeParserBuffer, ParsedFileInfo parsedFileInfo, Code code) { StringBuilder encodedImage = new(); //Read the image header info that is currently in the code string[] thumbnailTokens = code.Comment.Trim().Split(' '); //Stop processing since the thumbnail may be corrupt. if (thumbnailTokens.Length != 4) { throw new ImageProcessingException(); } string[] dimensions = thumbnailTokens[2].Split('x'); if (dimensions.Length != 2) { throw new ImageProcessingException(); } ParsedThumbnail thumbnail = new() { Width = int.Parse(dimensions[0]), Height = int.Parse(dimensions[1]) }; int encodedLength = int.Parse(thumbnailTokens[3]); encodedImage.Clear(); code.Reset(); //Keep reading the data from the file while (codeParserBuffer.GetPosition(reader) < reader.BaseStream.Length) { Program.CancellationToken.ThrowIfCancellationRequested(); if (!await Code.ParseAsync(reader, code, codeParserBuffer)) { continue; } if (code.Type != CodeType.Comment) { return; } if (string.IsNullOrEmpty(code.Comment)) { code.Reset(); continue; } if (code.Comment.Contains("thumbnail begin", StringComparison.InvariantCultureIgnoreCase)) { //Exit if we find another start tag before ending the previous image throw new ImageProcessingException(); } else if (code.Comment.Contains("thumbnail end", StringComparison.InvariantCultureIgnoreCase)) { if (encodedImage.Length == encodedLength) { thumbnail.EncodedImage = "data:image/png;base64," + encodedImage.ToString(); parsedFileInfo.Thumbnails.Add(thumbnail); return; } } else { encodedImage.Append(code.Comment.Trim()); } code.Reset(); } } }
/// <summary> /// Parse the header of a G-code file /// </summary> /// <param name="reader">Stream reader</param> /// <param name="partialFileInfo">G-code file information</param> /// <returns>Asynchronous task</returns> private static async Task ParseHeader(StreamReader reader, ParsedFileInfo partialFileInfo) { Code code = new Code(); CodeParserBuffer codeParserBuffer = new CodeParserBuffer(Settings.FileBufferSize, true); bool inRelativeMode = false, lastCodeHadInfo = false, gotNewInfo = false; long fileReadLimit = Math.Min(Settings.FileInfoReadLimitHeader + Settings.FileBufferSize, reader.BaseStream.Length); while (codeParserBuffer.GetPosition(reader) < fileReadLimit) { Program.CancellationToken.ThrowIfCancellationRequested(); if (!await DuetAPI.Commands.Code.ParseAsync(reader, code, codeParserBuffer)) { continue; } if (code.Type == CodeType.GCode && partialFileInfo.FirstLayerHeight == 0) { if (code.MajorNumber == 91) { // G91 code (relative positioning) inRelativeMode = true; gotNewInfo = true; } else if (inRelativeMode) { // G90 (absolute positioning) inRelativeMode = (code.MajorNumber != 90); gotNewInfo = true; } else if (code.MajorNumber == 0 || code.MajorNumber == 1) { // G0/G1 is a move, see if there is a Z parameter present CodeParameter zParam = code.Parameter('Z'); if (zParam != null) { float z = zParam; if (z <= Settings.MaxLayerHeight) { partialFileInfo.FirstLayerHeight = z; gotNewInfo = true; } } } } else if (!string.IsNullOrWhiteSpace(code.Comment)) { gotNewInfo |= (partialFileInfo.LayerHeight == 0) && FindLayerHeight(code.Comment, ref partialFileInfo); gotNewInfo |= FindFilamentUsed(code.Comment, ref partialFileInfo); gotNewInfo |= string.IsNullOrEmpty(partialFileInfo.GeneratedBy) && FindGeneratedBy(code.Comment, ref partialFileInfo); gotNewInfo |= (partialFileInfo.PrintTime == null) && FindPrintTime(code.Comment, ref partialFileInfo); gotNewInfo |= (partialFileInfo.SimulatedTime == null) && FindSimulatedTime(code.Comment, ref partialFileInfo); } // Is the file info complete? if (!gotNewInfo && !lastCodeHadInfo && IsFileInfoComplete(partialFileInfo)) { break; } lastCodeHadInfo = gotNewInfo; code.Reset(); } }
/// <summary> /// Parse the file for thumbnails /// </summary> /// <param name="reader">Stream reader</param> /// <param name="partialFileInfo">G-code file information</param> /// <returns>Asynchronous task</returns> /// <remarks> /// This functionality should be moved to ParseHeader in the long term /// </remarks> private static async Task ParseThumbnails(StreamReader reader, ParsedFileInfo parsedFileInfo) { Code code = new Code(); CodeParserBuffer codeParserBuffer = new CodeParserBuffer(Settings.FileBufferSize, true); bool imageFound = false; int encodedLength = 0; StringBuilder encodedImage = new StringBuilder(); reader.BaseStream.Seek(0, SeekOrigin.Begin); ParsedThumbnail thumbnail = null; while (codeParserBuffer.GetPosition(reader) < reader.BaseStream.Length) { Program.CancellationToken.ThrowIfCancellationRequested(); if (!await DuetAPI.Commands.Code.ParseAsync(reader, code, codeParserBuffer)) { continue; } if (code.Type != CodeType.Comment) { return; } if (string.IsNullOrEmpty(code.Comment)) { code.Reset(); continue; } if (code.Comment.Contains("thumbnail begin", StringComparison.InvariantCultureIgnoreCase)) { //Exit if we find another start tag before ending the previous image if (imageFound) { return; } string[] thumbnailTokens = code.Comment.Trim().Split(' '); //Stop processing since the thumbnail may be corrupt. if (thumbnailTokens.Length != 4) { return; } string[] dimensions = thumbnailTokens[2].Split('x'); if (dimensions.Length != 2) { continue; } imageFound = true; thumbnail = new ParsedThumbnail { Width = int.Parse(dimensions[0]), Height = int.Parse(dimensions[1]) }; encodedLength = int.Parse(thumbnailTokens[3]); encodedImage.Clear(); code.Reset(); continue; } else if (code.Comment.Contains("thumbnail end", StringComparison.InvariantCultureIgnoreCase)) { if (encodedImage.Length == encodedLength) { thumbnail.EncodedImage = "data:image/png;base64," + encodedImage.ToString(); parsedFileInfo.Thumbnails.Add(thumbnail); } thumbnail = null; imageFound = false; } else if (imageFound) { encodedImage.Append(code.Comment.Trim()); } code.Reset(); } }