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
This commit is contained in:
John Lewin 2019-06-27 09:27:16 -07:00 committed by jlewin
parent a7821d2e26
commit 9523ae320f
6 changed files with 427 additions and 436 deletions

View file

@ -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<bool> Slice(IObject3D object3D, IEnumerable<IObject3D> printableItems, PrinterSettings printerSettings, string filePath, IProgress<ProgressStatus> progressReporter, CancellationToken cancellationToken);
Dictionary<string, ExportField> Exports { get; }

View file

@ -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();

View file

@ -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))

View file

@ -36,12 +36,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
public class SliceProgressReporter : IProgress<ProgressStatus>
{
private IProgress<ProgressStatus> reporter;
private PrinterConfig printer;
public SliceProgressReporter(IProgress<ProgressStatus> reporter, PrinterConfig printer)
public SliceProgressReporter(IProgress<ProgressStatus> 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);

View file

@ -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<Mesh, MeshPrintOutputSettings> meshPrintOutputSettings = new Dictionary<Mesh, MeshPrintOutputSettings>();
private readonly HashSet<string> matterSliceSettingNames;
public Dictionary<string, ExportField> Exports { get; }
// Singleton use only - prevent external construction
public EngineMappingsMatterSlice(PrinterConfig printer2)
public EngineMappingsMatterSlice()
{
Exports = new Dictionary<string, ExportField>()
{
@ -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<IObject3D> printableItems, PrinterSettings settings, IProgress<ProgressStatus> 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<IEnumerable<IObject3D>>();
int extruderCount = settings.GetValue<int>(SettingsKey.extruder_count);
// Make sure we only consider 1 extruder if in spiral vase mode
if (settings.GetValue<bool>(SettingsKey.spiral_vase))
{
extruderCount = 1;
}
for (int extruderIndexIn = 0; extruderIndexIn < extruderCount; extruderIndexIn++)
{
var extruderIndex = extruderIndexIn;
IEnumerable<IObject3D> 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<bool> Slice(IObject3D object3D, IEnumerable<IObject3D> printableItems, PrinterSettings settings, string gcodeFilePath, IProgress<ProgressStatus> 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<IObject3D> 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;
}
}
}
}

View file

@ -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<Mesh, MeshPrintOutputSettings> meshPrintOutputSettings = new Dictionary<Mesh, MeshPrintOutputSettings>();
public static List<bool> ExtrudersUsed = new List<bool>();
public static bool RunInProcess { get; set; } = false;
public static void GetExtrudersUsed(List<bool> extrudersUsed, IObject3D object3D, PrinterConfig printer, bool checkForMeshFile)
public static void GetExtrudersUsed(List<bool> extrudersUsed, IEnumerable<IObject3D> 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<int>(SettingsKey.extruder_count);
int extruderCount = settings.GetValue<int>(SettingsKey.extruder_count);
// Make sure we only consider 1 extruder if in spiral vase mode
if (printer.Settings.GetValue<bool>(SettingsKey.spiral_vase)
if (settings.GetValue<bool>(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<int>(SettingsKey.support_material_extruder) != 0)
if (settings.GetValue<int>(SettingsKey.support_material_extruder) != 0)
{
int supportExtruder = Math.Max(0, Math.Min(extruderCount - 1, printer.Settings.GetValue<int>(SettingsKey.support_material_extruder) - 1));
int supportExtruder = Math.Max(0, Math.Min(extruderCount - 1, settings.GetValue<int>(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<bool>(SettingsKey.create_raft))
if (settings.GetValue<bool>(SettingsKey.create_raft))
{
if (printer.Settings.GetValue<int>(SettingsKey.raft_extruder) != 0)
if (settings.GetValue<int>(SettingsKey.raft_extruder) != 0)
{
int raftExtruder = Math.Max(0, Math.Min(extruderCount - 1, printer.Settings.GetValue<int>(SettingsKey.raft_extruder) - 1));
int raftExtruder = Math.Max(0, Math.Min(extruderCount - 1, settings.GetValue<int>(SettingsKey.raft_extruder) - 1));
extrudersUsed[raftExtruder] = true;
}
}
for (int extruderIndex = 0; extruderIndex < extruderCount; extruderIndex++)
{
IEnumerable<IObject3D> itemsThisExtruder = GetItemsForExtruder(meshItemsOnBuildPlate, extruderCount, extruderIndex, checkForMeshFile);
IEnumerable<IObject3D> 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<bool>();
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<ProgressStatus> 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<IEnumerable<IObject3D>>();
int extruderCount = printer.Settings.GetValue<int>(SettingsKey.extruder_count);
// Make sure we only consider 1 extruder if in spiral vase mode
if (printer.Settings.GetValue<bool>(SettingsKey.spiral_vase))
{
extruderCount = 1;
}
for (int extruderIndexIn = 0; extruderIndexIn < extruderCount; extruderIndexIn++)
{
var extruderIndex = extruderIndexIn;
IEnumerable<IObject3D> 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<IObject3D> GetItemsForExtruder(IEnumerable<IObject3D> meshItemsOnBuildPlate, int extruderCount, int extruderIndex, bool checkForMeshFile)
public static IEnumerable<IObject3D> GetItemsForExtruder(IEnumerable<IObject3D> 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<IObject3D> 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<bool> SliceItem(IObject3D object3D, string gcodeFilePath, PrinterConfig printer, IProgress<ProgressStatus> 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<bool> SliceItem(List<(Matrix4X4 matrix, string fileName)> stlFileLocations, string mergeRules, string gcodeFilePath, PrinterConfig printer, IProgress<ProgressStatus> 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);
}
}
}