From 9523ae320fbc386c384974164326c53ac4f311f6 Mon Sep 17 00:00:00 2001 From: John Lewin Date: Thu, 27 Jun 2019 09:27:16 -0700 Subject: [PATCH] Move MatterSlice style slicing to EngineMappingMatterSlice - Convert EngineMappingMatterSlice to IObjectSlicer with classic slicing - Update IObjectSlicer to support tasks, cancellation, progress - Remove Printer couplings - Remove printer reference from SliceProgressReporter --- .../Settings/IObjectSlicer.cs | 8 +- .../ApplicationView/ApplicationController.cs | 7 +- .../RunningTaskStatusPanel.cs | 8 + .../View3D/SliceProgressReporter.cs | 10 +- .../EngineMappingsMatterSlice.cs | 395 +++++++++++++++- .../SlicerConfiguration/Slicer.cs | 435 +----------------- 6 files changed, 427 insertions(+), 436 deletions(-) diff --git a/MatterControl.Printing/Settings/IObjectSlicer.cs b/MatterControl.Printing/Settings/IObjectSlicer.cs index 5727d7ae4..d28f501ec 100644 --- a/MatterControl.Printing/Settings/IObjectSlicer.cs +++ b/MatterControl.Printing/Settings/IObjectSlicer.cs @@ -27,14 +27,18 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -using System.IO; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MatterHackers.Agg; using MatterHackers.DataConverters3D; namespace MatterHackers.MatterControl.SlicerConfiguration { public interface IObjectSlicer { - bool Slice(IObject3D object3D, PrinterSettings printerSettings, Stream outputStream); + Task Slice(IObject3D object3D, IEnumerable printableItems, PrinterSettings printerSettings, string filePath, IProgress progressReporter, CancellationToken cancellationToken); Dictionary Exports { get; } diff --git a/MatterControlLib/ApplicationView/ApplicationController.cs b/MatterControlLib/ApplicationView/ApplicationController.cs index 7973b8ad7..f433484b5 100644 --- a/MatterControlLib/ApplicationView/ApplicationController.cs +++ b/MatterControlLib/ApplicationView/ApplicationController.cs @@ -4100,8 +4100,11 @@ Support and tutorials: { try { - // TODO: replace with runtime switchable solution - PrinterSettings.Slicer = new EngineMappingsMatterSlice(); + if (PrinterSettings.Slicer == null) + { + // TODO: replace with runtime switchable solution + PrinterSettings.Slicer = new EngineMappingsMatterSlice(); + } // Initial load builds UI elements, then constructs workspace tabs as they're encountered in RestoreUserTabs() await applicationController.RestoreUserTabs(); diff --git a/MatterControlLib/PartPreviewWindow/RunningTaskStatusPanel.cs b/MatterControlLib/PartPreviewWindow/RunningTaskStatusPanel.cs index c3e8ad25d..92fa9e3b5 100644 --- a/MatterControlLib/PartPreviewWindow/RunningTaskStatusPanel.cs +++ b/MatterControlLib/PartPreviewWindow/RunningTaskStatusPanel.cs @@ -91,6 +91,14 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private void TaskDetails_ProgressChanged(object sender, ProgressStatus e) { + if (e.Target == "terminal" + && sender is RunningTaskDetails details + && details.Owner is PrinterConfig printer) + { + printer.Connection.TerminalLog.WriteLine(e.Status); + return; + } + if (textWidget.Text != e.Status && !string.IsNullOrEmpty(e.Status) && !textWidget.Text.Contains(e.Status, StringComparison.OrdinalIgnoreCase)) diff --git a/MatterControlLib/PartPreviewWindow/View3D/SliceProgressReporter.cs b/MatterControlLib/PartPreviewWindow/View3D/SliceProgressReporter.cs index 2f6f84958..262e75441 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/SliceProgressReporter.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/SliceProgressReporter.cs @@ -36,12 +36,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public class SliceProgressReporter : IProgress { private IProgress reporter; - private PrinterConfig printer; - public SliceProgressReporter(IProgress reporter, PrinterConfig printer) + public SliceProgressReporter(IProgress reporter) { this.reporter = reporter; - this.printer = printer; } public void Report(ProgressStatus progressStatus) @@ -50,7 +48,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow double destValue = 10; string statusText = progressStatus.Status = progressStatus.Status.Trim().TrimEnd('.'); - + if (GCodeFile.GetFirstNumberAfter("", statusText, ref currentValue) && GCodeFile.GetFirstNumberAfter("/", statusText, ref destValue)) { @@ -60,10 +58,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } progressStatus.Progress0To1 = currentValue / destValue; + progressStatus.Target = null; } else { - printer.Connection.TerminalLog.WriteLine(statusText); + progressStatus.Status = statusText; + progressStatus.Target = "terminal"; } reporter.Report(progressStatus); diff --git a/MatterControlLib/SlicerConfiguration/EngineMappingsMatterSlice.cs b/MatterControlLib/SlicerConfiguration/EngineMappingsMatterSlice.cs index e65827182..396a1fe2a 100644 --- a/MatterControlLib/SlicerConfiguration/EngineMappingsMatterSlice.cs +++ b/MatterControlLib/SlicerConfiguration/EngineMappingsMatterSlice.cs @@ -29,22 +29,35 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using MatterHackers.Agg; +using MatterHackers.Agg.Platform; using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.DataStorage; +using MatterHackers.MatterControl.DesignTools; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.MatterControl.SettingsManagement; +using MatterHackers.PolygonMesh; +using MatterHackers.PolygonMesh.Processors; +using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.SlicerConfiguration { public class EngineMappingsMatterSlice : IObjectSlicer { + private static Dictionary meshPrintOutputSettings = new Dictionary(); private readonly HashSet matterSliceSettingNames; public Dictionary Exports { get; } // Singleton use only - prevent external construction - public EngineMappingsMatterSlice(PrinterConfig printer2) + public EngineMappingsMatterSlice() { Exports = new Dictionary() { @@ -204,12 +217,6 @@ namespace MatterHackers.MatterControl.SlicerConfiguration } } - { - public bool Slice(IObject3D object3D, PrinterSettings printerSettings, Stream outputStream) - { - throw new NotImplementedException(); - } - public bool ValidateFile(string filePath) { // read the last few k of the file and see if it says "filament used". We use this marker to tell if the file finished writing @@ -242,6 +249,377 @@ namespace MatterHackers.MatterControl.SlicerConfiguration } } + public static List<(Matrix4X4 matrix, string fileName)> GetStlFileLocations(IObject3D object3D, ref string mergeRules, IEnumerable printableItems, PrinterSettings settings, IProgress reporter, CancellationToken cancellationToken) + { + var progressStatus = new ProgressStatus(); + + Slicer.GetExtrudersUsed(Slicer.ExtrudersUsed, printableItems, object3D, settings, true); + + // TODO: Once graph parsing is added to MatterSlice we can remove and avoid this flattening + meshPrintOutputSettings.Clear(); + + // Flatten the scene, filtering out items outside of the build volume + var meshItemsOnBuildPlate = printableItems; + + if (meshItemsOnBuildPlate.Any()) + { + int maxExtruderIndex = 0; + + var itemsByExtruder = new List>(); + int extruderCount = settings.GetValue(SettingsKey.extruder_count); + // Make sure we only consider 1 extruder if in spiral vase mode + if (settings.GetValue(SettingsKey.spiral_vase)) + { + extruderCount = 1; + } + + for (int extruderIndexIn = 0; extruderIndexIn < extruderCount; extruderIndexIn++) + { + var extruderIndex = extruderIndexIn; + IEnumerable itemsThisExtruder = Slicer.GetItemsForExtruder(meshItemsOnBuildPlate, extruderCount, extruderIndex, true); + + itemsByExtruder.Add(itemsThisExtruder); + if (Slicer.ExtrudersUsed[extruderIndex]) + { + maxExtruderIndex = extruderIndex; + } + } + + var outputOptions = new List<(Matrix4X4 matrix, string fileName)>(); + + int savedStlCount = 0; + bool first = true; + for (int extruderIndex = 0; extruderIndex < itemsByExtruder.Count; extruderIndex++) + { + if (!first) + { + mergeRules += ","; + first = false; + } + + mergeRules += AddObjectsForExtruder(itemsByExtruder[extruderIndex], outputOptions, ref savedStlCount); + } + + var supportObjects = meshItemsOnBuildPlate.Where((item) => item.WorldOutputType() == PrintOutputTypes.Support); + // if we added user generated support + if (supportObjects.Any()) + { + // add a flag to the merge rules to let us know there was support + mergeRules += ",S" + AddObjectsForExtruder(supportObjects, outputOptions, ref savedStlCount); + } + + var wipeTowerObjects = meshItemsOnBuildPlate.Where((item) => item.WorldOutputType() == PrintOutputTypes.WipeTower); + // if we added user generated wipe tower + if (wipeTowerObjects.Any()) + { + // add a flag to the merge rules to let us know there was a wipe tower + mergeRules += ",W" + AddObjectsForExtruder(wipeTowerObjects, outputOptions, ref savedStlCount); + } + + mergeRules += " "; + + return outputOptions; + } + + return new List<(Matrix4X4 matrix, string fileName)>(); + } + + public Task Slice(IObject3D object3D, IEnumerable printableItems, PrinterSettings settings, string gcodeFilePath, IProgress reporter, CancellationToken cancellationToken) + { + string mergeRules = ""; + + var stlFileLocations = GetStlFileLocations(object3D, ref mergeRules, printableItems, settings, reporter, cancellationToken); + + if (stlFileLocations.Count <= 0) + { + return Task.FromResult(false); + } + + // Wrap the reporter with a specialized MatterSlice string parser for percent from string results + var sliceProgressReporter = new SliceProgressReporter(reporter); + + bool slicingSucceeded = true; + + if (stlFileLocations.Count > 0) + { + var progressStatus = new ProgressStatus() + { + Status = "Generating Config" + }; + sliceProgressReporter.Report(progressStatus); + + string configFilePath = Path.Combine( + ApplicationDataStorage.Instance.GCodeOutputPath, + string.Format("config_{0}.ini", settings.GetGCodeCacheKey().ToString())); + + progressStatus.Status = "Starting slicer"; + sliceProgressReporter.Report(progressStatus); + + if (!File.Exists(gcodeFilePath) + || !HasCompletedSuccessfully(gcodeFilePath)) + { + string commandArgs; + + var matrixAndMeshArgs = new StringBuilder(); + foreach (var (matrix, fileName) in stlFileLocations) + { + var matrixString = ""; + bool first = true; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (!first) + { + matrixString += ","; + } + + matrixString += matrix[i, j].ToString("0.######"); + first = false; + } + } + + matrixAndMeshArgs.Append($" -m \"{matrixString}\""); + matrixAndMeshArgs.Append($" \"{fileName}\" "); + } + + this.WriteSliceSettingsFile( + configFilePath, + new[] + { + $"booleanOperations = {mergeRules}", + $"additionalArgsToProcess ={matrixAndMeshArgs}" + }, + settings); + + commandArgs = $"-v -o \"{gcodeFilePath}\" -c \"{configFilePath}\""; + + bool forcedExit = false; + + if (AggContext.OperatingSystem == OSType.Android + || AggContext.OperatingSystem == OSType.Mac + || Slicer.RunInProcess) + { + void WriteOutput(object s, EventArgs e) + { + if (cancellationToken.IsCancellationRequested) + { + MatterHackers.MatterSlice.MatterSlice.Stop(); + forcedExit = true; + } + + if (s is string stringValue) + { + sliceProgressReporter?.Report(new ProgressStatus() + { + Status = stringValue + }); + } + } + + MatterSlice.LogOutput.GetLogWrites += WriteOutput; + + MatterSlice.MatterSlice.ProcessArgs(commandArgs); + + MatterSlice.LogOutput.GetLogWrites -= WriteOutput; + + slicingSucceeded = !forcedExit; + } + else + { + var slicerProcess = new Process() + { + StartInfo = new ProcessStartInfo() + { + Arguments = commandArgs, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardError = true, + RedirectStandardOutput = true, + FileName = MatterSliceInfo.GetEnginePath(), + UseShellExecute = false + } + }; + + slicerProcess.OutputDataReceived += (s, e) => + { + if (e.Data is string stringValue) + { + if (cancellationToken.IsCancellationRequested) + { + slicerProcess?.Kill(); + slicerProcess?.Dispose(); + forcedExit = true; + } + + string message = stringValue.Replace("=>", "").Trim(); + if (message.Contains(".gcode")) + { + message = "Saving intermediate file"; + } + + message += "..."; + + sliceProgressReporter?.Report(new ProgressStatus() + { + Status = message + }); + } + }; + + slicerProcess.Start(); + slicerProcess.BeginOutputReadLine(); + + string stdError = slicerProcess.StandardError.ReadToEnd(); + + if (!forcedExit) + { + slicerProcess.WaitForExit(); + } + + slicingSucceeded = !forcedExit; + } + } + + try + { + if (slicingSucceeded + && File.Exists(gcodeFilePath) + && File.Exists(configFilePath)) + { + // make sure we have not already written the settings onto this file + bool fileHasSettings = false; + int bufferSize = 32000; + using (Stream fileStream = File.OpenRead(gcodeFilePath)) + { + // Read the tail of the file to determine if the given token exists + byte[] buffer = new byte[bufferSize]; + fileStream.Seek(Math.Max(0, fileStream.Length - bufferSize), SeekOrigin.Begin); + int numBytesRead = fileStream.Read(buffer, 0, bufferSize); + string fileEnd = System.Text.Encoding.UTF8.GetString(buffer); + if (fileEnd.Contains("GCode settings used")) + { + fileHasSettings = true; + } + } + + if (!fileHasSettings) + { + using (StreamWriter gcodeWriter = File.AppendText(gcodeFilePath)) + { + string oemName = "MatterControl"; + if (OemSettings.Instance.WindowTitleExtra != null && OemSettings.Instance.WindowTitleExtra.Trim().Length > 0) + { + oemName += $" - {OemSettings.Instance.WindowTitleExtra}"; + } + + gcodeWriter.WriteLine("; {0} Version {1} Build {2} : GCode settings used", oemName, VersionInfo.Instance.ReleaseVersion, VersionInfo.Instance.BuildVersion); + gcodeWriter.WriteLine("; Date {0} Time {1}:{2:00}", DateTime.Now.Date, DateTime.Now.Hour, DateTime.Now.Minute); + + var settingsToSkip = new string[] { "booleanOperations", "additionalArgsToProcess" }; + foreach (string line in File.ReadLines(configFilePath)) + { + if (!settingsToSkip.Any(setting => line.StartsWith(setting))) + { + gcodeWriter.WriteLine("; {0}", line); + } + } + } + } + } + } + catch (Exception) + { + } + } + + return Task.FromResult(slicingSucceeded); + } + + private static bool HasCompletedSuccessfully(string gcodeFilePath) + { + using (var reader = new StreamReader(gcodeFilePath)) + { + int pageSize = 10000; + var fileStream = reader.BaseStream; + + long position = reader.BaseStream.Length - pageSize; + + // Process through the stream until we find the slicing success token or we pass the start + while (position > 0) + { + fileStream.Position = position; + + string tail = reader.ReadToEnd(); + + // Read from current position to the end + if (tail.Contains("; MatterSlice Completed Successfully")) + { + return true; + } + + // Page further back in the stream and retry + position -= pageSize; + } + + return false; + } + } + + private static string AddObjectsForExtruder(IEnumerable items, List<(Matrix4X4 matrix, string fileName)> outputItems, ref int savedStlCount) + { + string mergeString = ""; + if (items.Any()) + { + bool first = true; + foreach (var item in items) + { + if (!first) + { + mergeString += ","; + } + + // TODO: Use existing AssetsPath property + string assetsDirectory = Path.Combine(ApplicationDataStorage.Instance.ApplicationLibraryDataPath, "Assets"); + var itemWorldMatrix = item.WorldMatrix(); + if (item is GeneratedSupportObject3D generatedSupportObject3D + && item.Mesh != null) + { + // grow the support columns by the amount they are reduced by + var aabbForCenter = item.Mesh.GetAxisAlignedBoundingBox(); + var aabbForSize = item.Mesh.GetAxisAlignedBoundingBox(item.Matrix); + var xyScale = (aabbForSize.XSize + 2 * SupportGenerator.ColumnReduceAmount) / aabbForSize.XSize; + itemWorldMatrix = itemWorldMatrix.ApplyAtPosition(aabbForCenter.Center.Transform(itemWorldMatrix), Matrix4X4.CreateScale(xyScale, xyScale, 1)); + } + + outputItems.Add((itemWorldMatrix, Path.Combine(assetsDirectory, item.MeshPath))); + mergeString += $"({savedStlCount++}"; + first = false; + } + + mergeString += new string(')', items.Count()); + } + else + { + // TODO: consider dropping the custom path and using the AssetPath as above + string folderToSaveStlsTo = Path.Combine(ApplicationDataStorage.Instance.ApplicationTempDataPath, "amf_to_stl"); + + // Create directory if needed + Directory.CreateDirectory(folderToSaveStlsTo); + + Mesh tinyMesh = PlatonicSolids.CreateCube(.001, .001, .001); + + string tinyObjectFileName = Path.Combine(folderToSaveStlsTo, Path.ChangeExtension("non_printing_extruder_change_mesh", ".stl")); + + StlProcessing.Save(tinyMesh, tinyObjectFileName, CancellationToken.None); + + outputItems.Add((Matrix4X4.Identity, tinyObjectFileName)); + mergeString += $"({savedStlCount++})"; + } + + return mergeString; + } + public static class StartGCodeGenerator { public static string BuildStartGCode(PrinterSettings settings, string userGCode) @@ -417,8 +795,5 @@ namespace MatterHackers.MatterControl.SlicerConfiguration return false; } } - - - } } \ No newline at end of file diff --git a/MatterControlLib/SlicerConfiguration/Slicer.cs b/MatterControlLib/SlicerConfiguration/Slicer.cs index d72d58d42..95fb5a4b7 100644 --- a/MatterControlLib/SlicerConfiguration/Slicer.cs +++ b/MatterControlLib/SlicerConfiguration/Slicer.cs @@ -29,47 +29,34 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using MatterHackers.Agg; -using MatterHackers.Agg.Platform; using MatterHackers.DataConverters3D; -using MatterHackers.MatterControl.DataStorage; -using MatterHackers.MatterControl.DesignTools; -using MatterHackers.MatterControl.DesignTools.Operations; -using MatterHackers.MatterControl.PartPreviewWindow; -using MatterHackers.MatterControl.SettingsManagement; using MatterHackers.PolygonMesh; -using MatterHackers.PolygonMesh.Processors; -using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.SlicerConfiguration { public static class Slicer { - private static Dictionary meshPrintOutputSettings = new Dictionary(); - public static List ExtrudersUsed = new List(); public static bool RunInProcess { get; set; } = false; - public static void GetExtrudersUsed(List extrudersUsed, IObject3D object3D, PrinterConfig printer, bool checkForMeshFile) + public static void GetExtrudersUsed(List extrudersUsed, IEnumerable printableItems, IObject3D object3D, PrinterSettings settings, bool checkForMeshFile) { extrudersUsed.Clear(); - var meshItemsOnBuildPlate = printer.PrintableItems(object3D); - if (!meshItemsOnBuildPlate.Any()) + if (!printableItems.Any()) { return; } - int extruderCount = printer.Settings.GetValue(SettingsKey.extruder_count); + int extruderCount = settings.GetValue(SettingsKey.extruder_count); // Make sure we only consider 1 extruder if in spiral vase mode - if (printer.Settings.GetValue(SettingsKey.spiral_vase) + if (settings.GetValue(SettingsKey.spiral_vase) && extrudersUsed.Count(used => used == true) > 1) { extruderCount = 1; @@ -83,34 +70,37 @@ namespace MatterHackers.MatterControl.SlicerConfiguration // If we have support enabled and are using an extruder other than 0 for it if (object3D.VisibleMeshes().Any(i => i.WorldOutputType() == PrintOutputTypes.Support)) { - if (printer.Settings.GetValue(SettingsKey.support_material_extruder) != 0) + if (settings.GetValue(SettingsKey.support_material_extruder) != 0) { - int supportExtruder = Math.Max(0, Math.Min(extruderCount - 1, printer.Settings.GetValue(SettingsKey.support_material_extruder) - 1)); + int supportExtruder = Math.Max(0, Math.Min(extruderCount - 1, settings.GetValue(SettingsKey.support_material_extruder) - 1)); extrudersUsed[supportExtruder] = true; } } // If we have raft enabled and are using an extruder other than 0 for it - if (printer.Settings.GetValue(SettingsKey.create_raft)) + if (settings.GetValue(SettingsKey.create_raft)) { - if (printer.Settings.GetValue(SettingsKey.raft_extruder) != 0) + if (settings.GetValue(SettingsKey.raft_extruder) != 0) { - int raftExtruder = Math.Max(0, Math.Min(extruderCount - 1, printer.Settings.GetValue(SettingsKey.raft_extruder) - 1)); + int raftExtruder = Math.Max(0, Math.Min(extruderCount - 1, settings.GetValue(SettingsKey.raft_extruder) - 1)); extrudersUsed[raftExtruder] = true; } } for (int extruderIndex = 0; extruderIndex < extruderCount; extruderIndex++) { - IEnumerable itemsThisExtruder = GetItemsForExtruder(meshItemsOnBuildPlate, extruderCount, extruderIndex, checkForMeshFile); + IEnumerable itemsThisExtruder = GetItemsForExtruder(printableItems, extruderCount, extruderIndex, checkForMeshFile); extrudersUsed[extruderIndex] |= itemsThisExtruder.Any(); } } public static bool T1OrGreaterUsed(PrinterConfig printer) { + var scene = printer.Bed.Scene; + var extrudersUsed = new List(); - Slicer.GetExtrudersUsed(extrudersUsed, printer.Bed.Scene, printer, false); + Slicer.GetExtrudersUsed(extrudersUsed, printer.PrintableItems(scene), scene, printer.Settings, false); + for (int i = 1; i < extrudersUsed.Count; i++) { if (extrudersUsed[i]) @@ -122,81 +112,7 @@ namespace MatterHackers.MatterControl.SlicerConfiguration return false; } - public static List<(Matrix4X4 matrix, string fileName)> GetStlFileLocations(IObject3D object3D, ref string mergeRules, PrinterConfig printer, IProgress progressReporter, CancellationToken cancellationToken) - { - var progressStatus = new ProgressStatus(); - - GetExtrudersUsed(ExtrudersUsed, object3D, printer, true); - // TODO: Once graph parsing is added to MatterSlice we can remove and avoid this flattening - meshPrintOutputSettings.Clear(); - - // Flatten the scene, filtering out items outside of the build volume - var meshItemsOnBuildPlate = printer.PrintableItems(object3D); - - if (meshItemsOnBuildPlate.Any()) - { - int maxExtruderIndex = 0; - - var itemsByExtruder = new List>(); - int extruderCount = printer.Settings.GetValue(SettingsKey.extruder_count); - // Make sure we only consider 1 extruder if in spiral vase mode - if (printer.Settings.GetValue(SettingsKey.spiral_vase)) - { - extruderCount = 1; - } - - for (int extruderIndexIn = 0; extruderIndexIn < extruderCount; extruderIndexIn++) - { - var extruderIndex = extruderIndexIn; - IEnumerable itemsThisExtruder = GetItemsForExtruder(meshItemsOnBuildPlate, extruderCount, extruderIndex, true); - - itemsByExtruder.Add(itemsThisExtruder); - if (ExtrudersUsed[extruderIndex]) - { - maxExtruderIndex = extruderIndex; - } - } - - var outputOptions = new List<(Matrix4X4 matrix, string fileName)>(); - - int savedStlCount = 0; - bool first = true; - for (int extruderIndex = 0; extruderIndex < itemsByExtruder.Count; extruderIndex++) - { - if (!first) - { - mergeRules += ","; - first = false; - } - - mergeRules += AddObjectsForExtruder(itemsByExtruder[extruderIndex], outputOptions, ref savedStlCount); - } - - var supportObjects = meshItemsOnBuildPlate.Where((item) => item.WorldOutputType() == PrintOutputTypes.Support); - // if we added user generated support - if (supportObjects.Any()) - { - // add a flag to the merge rules to let us know there was support - mergeRules += ",S" + AddObjectsForExtruder(supportObjects, outputOptions, ref savedStlCount); - } - - var wipeTowerObjects = meshItemsOnBuildPlate.Where((item) => item.WorldOutputType() == PrintOutputTypes.WipeTower); - // if we added user generated wipe tower - if (wipeTowerObjects.Any()) - { - // add a flag to the merge rules to let us know there was a wipe tower - mergeRules += ",W" + AddObjectsForExtruder(wipeTowerObjects, outputOptions, ref savedStlCount); - } - - mergeRules += " "; - - return outputOptions; - } - - return new List<(Matrix4X4 matrix, string fileName)>(); - } - - private static IEnumerable GetItemsForExtruder(IEnumerable meshItemsOnBuildPlate, int extruderCount, int extruderIndex, bool checkForMeshFile) + public static IEnumerable GetItemsForExtruder(IEnumerable meshItemsOnBuildPlate, int extruderCount, int extruderIndex, bool checkForMeshFile) { var itemsThisExtruder = meshItemsOnBuildPlate.Where((item) => (!checkForMeshFile || (File.Exists(item.MeshPath) // Drop missing files @@ -205,330 +121,15 @@ namespace MatterHackers.MatterControl.SlicerConfiguration || (extruderIndex == 0 && (item.WorldMaterialIndex() >= extruderCount || item.WorldMaterialIndex() == -1))) && (item.WorldOutputType() == PrintOutputTypes.Solid || item.WorldOutputType() == PrintOutputTypes.Default)); + return itemsThisExtruder; } - private static string AddObjectsForExtruder(IEnumerable items, - List<(Matrix4X4 matrix, string fileName)> outputItems, - ref int savedStlCount) - { - string mergeString = ""; - if (items.Any()) - { - bool first = true; - foreach (var item in items) - { - if (!first) - { - mergeString += ","; - } - - // TODO: Use existing AssetsPath property - string assetsDirectory = Path.Combine(ApplicationDataStorage.Instance.ApplicationLibraryDataPath, "Assets"); - var itemWorldMatrix = item.WorldMatrix(); - if (item is GeneratedSupportObject3D generatedSupportObject3D - && item.Mesh != null) - { - // grow the support columns by the amount they are reduced by - var aabbForCenter = item.Mesh.GetAxisAlignedBoundingBox(); - var aabbForSize = item.Mesh.GetAxisAlignedBoundingBox(item.Matrix); - var xyScale = (aabbForSize.XSize + 2 * SupportGenerator.ColumnReduceAmount) / aabbForSize.XSize; - itemWorldMatrix = itemWorldMatrix.ApplyAtPosition(aabbForCenter.Center.Transform(itemWorldMatrix), Matrix4X4.CreateScale(xyScale, xyScale, 1)); - } - - outputItems.Add((itemWorldMatrix, Path.Combine(assetsDirectory, item.MeshPath))); - mergeString += $"({savedStlCount++}"; - first = false; - } - - mergeString += new string(')', items.Count()); - } - else - { - // TODO: consider dropping the custom path and using the AssetPath as above - string folderToSaveStlsTo = Path.Combine(ApplicationDataStorage.Instance.ApplicationTempDataPath, "amf_to_stl"); - - // Create directory if needed - Directory.CreateDirectory(folderToSaveStlsTo); - - Mesh tinyMesh = PlatonicSolids.CreateCube(.001, .001, .001); - - string tinyObjectFileName = Path.Combine(folderToSaveStlsTo, Path.ChangeExtension("non_printing_extruder_change_mesh", ".stl")); - - StlProcessing.Save(tinyMesh, tinyObjectFileName, CancellationToken.None); - - outputItems.Add((Matrix4X4.Identity, tinyObjectFileName)); - mergeString += $"({savedStlCount++})"; - } - - return mergeString; - } - public static Task SliceItem(IObject3D object3D, string gcodeFilePath, PrinterConfig printer, IProgress progressReporter, CancellationToken cancellationToken) { - string mergeRules = ""; + var scene = printer.Bed.Scene; - var stlFileLocations = GetStlFileLocations(object3D, ref mergeRules, printer, progressReporter, cancellationToken); - - if (stlFileLocations.Count > 0) - { - return SliceItem(stlFileLocations, mergeRules, gcodeFilePath, printer, progressReporter, cancellationToken); - } - - return Task.FromResult(false); - } - - public static Task SliceItem(List<(Matrix4X4 matrix, string fileName)> stlFileLocations, string mergeRules, string gcodeFilePath, PrinterConfig printer, IProgress reporter, CancellationToken cancellationToken) - { - // Wrap the reporter with a specialized MatterSlice string parser for percent from string results - var sliceProgressReporter = new SliceProgressReporter(reporter, printer); - - bool slicingSucceeded = true; - - if (stlFileLocations.Count > 0) - { - var progressStatus = new ProgressStatus() - { - Status = "Generating Config" - }; - sliceProgressReporter.Report(progressStatus); - - string configFilePath = Path.Combine( - ApplicationDataStorage.Instance.GCodeOutputPath, - string.Format("config_{0}.ini", printer.Settings.GetGCodeCacheKey().ToString())); - - progressStatus.Status = "Starting slicer"; - sliceProgressReporter.Report(progressStatus); - - if (!File.Exists(gcodeFilePath) - || !HasCompletedSuccessfully(gcodeFilePath)) - { - string commandArgs; - - var matrixAndMeshArgs = new StringBuilder(); - foreach (var (matrix, fileName) in stlFileLocations) - { - var matrixString = ""; - bool first = true; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (!first) - { - matrixString += ","; - } - - matrixString += matrix[i, j].ToString("0.######"); - first = false; - } - } - - matrixAndMeshArgs.Append($" -m \"{matrixString}\""); - matrixAndMeshArgs.Append($" \"{fileName}\" "); - } - - // Map from PrinterSettings to PrintSettings - if (PrinterSettings.Slicer != null) - { - var children = stlFileLocations.Select(s => new Object3D() - { - MeshPath = s.fileName, - Matrix = s.matrix - }); - - using (var outputStream = File.OpenWrite(gcodeFilePath)) - { - PrinterSettings.Slicer.Slice( - new Object3D(children), - printer.Settings, - outputStream); - } - - return Task.FromResult(true); - } - - printer.EngineMappingsMatterSlice.WriteSliceSettingsFile( - configFilePath, - new[] - { - $"booleanOperations = {mergeRules}", - $"additionalArgsToProcess ={matrixAndMeshArgs}" - }, - printer.Settings); - - commandArgs = $"-v -o \"{gcodeFilePath}\" -c \"{configFilePath}\""; - - bool forcedExit = false; - - if (AggContext.OperatingSystem == OSType.Android - || AggContext.OperatingSystem == OSType.Mac - || RunInProcess) - { - void WriteOutput(object s, EventArgs e) - { - if (cancellationToken.IsCancellationRequested) - { - MatterHackers.MatterSlice.MatterSlice.Stop(); - forcedExit = true; - } - - if (s is string stringValue) - { - sliceProgressReporter?.Report(new ProgressStatus() - { - Status = stringValue - }); - } - } - - MatterSlice.LogOutput.GetLogWrites += WriteOutput; - - MatterSlice.MatterSlice.ProcessArgs(commandArgs); - - MatterSlice.LogOutput.GetLogWrites -= WriteOutput; - - slicingSucceeded = !forcedExit; - } - else - { - var slicerProcess = new Process() - { - StartInfo = new ProcessStartInfo() - { - Arguments = commandArgs, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - RedirectStandardError = true, - RedirectStandardOutput = true, - FileName = MatterSliceInfo.GetEnginePath(), - UseShellExecute = false - } - }; - - slicerProcess.OutputDataReceived += (s, e) => - { - if (e.Data is string stringValue) - { - if (cancellationToken.IsCancellationRequested) - { - slicerProcess?.Kill(); - slicerProcess?.Dispose(); - forcedExit = true; - } - - string message = stringValue.Replace("=>", "").Trim(); - if (message.Contains(".gcode")) - { - message = "Saving intermediate file"; - } - - message += "..."; - - sliceProgressReporter?.Report(new ProgressStatus() - { - Status = message - }); - } - }; - - slicerProcess.Start(); - slicerProcess.BeginOutputReadLine(); - - string stdError = slicerProcess.StandardError.ReadToEnd(); - - if (!forcedExit) - { - slicerProcess.WaitForExit(); - } - - slicingSucceeded = !forcedExit; - } - } - - try - { - if (slicingSucceeded - && File.Exists(gcodeFilePath) - && File.Exists(configFilePath)) - { - // make sure we have not already written the settings onto this file - bool fileHasSettings = false; - int bufferSize = 32000; - using (Stream fileStream = File.OpenRead(gcodeFilePath)) - { - // Read the tail of the file to determine if the given token exists - byte[] buffer = new byte[bufferSize]; - fileStream.Seek(Math.Max(0, fileStream.Length - bufferSize), SeekOrigin.Begin); - int numBytesRead = fileStream.Read(buffer, 0, bufferSize); - string fileEnd = System.Text.Encoding.UTF8.GetString(buffer); - if (fileEnd.Contains("GCode settings used")) - { - fileHasSettings = true; - } - } - - if (!fileHasSettings) - { - using (StreamWriter gcodeWriter = File.AppendText(gcodeFilePath)) - { - string oemName = "MatterControl"; - if (OemSettings.Instance.WindowTitleExtra != null && OemSettings.Instance.WindowTitleExtra.Trim().Length > 0) - { - oemName += $" - {OemSettings.Instance.WindowTitleExtra}"; - } - - gcodeWriter.WriteLine("; {0} Version {1} Build {2} : GCode settings used", oemName, VersionInfo.Instance.ReleaseVersion, VersionInfo.Instance.BuildVersion); - gcodeWriter.WriteLine("; Date {0} Time {1}:{2:00}", DateTime.Now.Date, DateTime.Now.Hour, DateTime.Now.Minute); - - var settingsToSkip = new string[] { "booleanOperations", "additionalArgsToProcess" }; - foreach (string line in File.ReadLines(configFilePath)) - { - if (!settingsToSkip.Any(setting => line.StartsWith(setting))) - { - gcodeWriter.WriteLine("; {0}", line); - } - } - } - } - } - } - catch (Exception) - { - } - } - - return Task.FromResult(slicingSucceeded); - } - - private static bool HasCompletedSuccessfully(string gcodeFilePath) - { - using (var reader = new StreamReader(gcodeFilePath)) - { - int pageSize = 10000; - var fileStream = reader.BaseStream; - - long position = reader.BaseStream.Length - pageSize; - - // Process through the stream until we find the slicing success token or we pass the start - while (position > 0) - { - fileStream.Position = position; - - string tail = reader.ReadToEnd(); - - // Read from current position to the end - if (tail.Contains("; MatterSlice Completed Successfully")) - { - return true; - } - - // Page further back in the stream and retry - position -= pageSize; - } - - return false; - } + return PrinterSettings.Slicer.Slice(object3D, printer.PrintableItems(scene), printer.Settings, gcodeFilePath, progressReporter, cancellationToken); } } }