diff --git a/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs b/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs index 1a82b41b0..dffc52fa9 100644 --- a/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs +++ b/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs @@ -128,15 +128,15 @@ namespace MatterHackers.GCodeVisualizer if (Math.Abs(eMovement) > 0) { // this is a retraction - renderFeaturesForLayer.Add(new RenderFeatureRetract(currentInstruction.Position, eMovement, currentInstruction.ToolIndex, currentInstruction.FeedRate)); + renderFeaturesForLayer.Add(new RenderFeatureRetract(instructionIndex, currentInstruction.Position, eMovement, currentInstruction.ToolIndex, currentInstruction.FeedRate)); } if (currentInstruction.Line.StartsWith("G10")) { - renderFeaturesForLayer.Add(new RenderFeatureRetract(currentInstruction.Position, -1, currentInstruction.ToolIndex, currentInstruction.FeedRate)); + renderFeaturesForLayer.Add(new RenderFeatureRetract(instructionIndex, currentInstruction.Position, -1, currentInstruction.ToolIndex, currentInstruction.FeedRate)); } else if (currentInstruction.Line.StartsWith("G11")) { - renderFeaturesForLayer.Add(new RenderFeatureRetract(currentInstruction.Position, 1, currentInstruction.ToolIndex, currentInstruction.FeedRate)); + renderFeaturesForLayer.Add(new RenderFeatureRetract(instructionIndex, currentInstruction.Position, 1, currentInstruction.ToolIndex, currentInstruction.FeedRate)); } } else @@ -148,6 +148,7 @@ namespace MatterHackers.GCodeVisualizer Color extrusionColor = ExtrusionColors.GetColorForSpeed((float)currentInstruction.FeedRate); renderFeaturesForLayer.Add( new RenderFeatureExtrusion( + instructionIndex, previousInstruction.Position, currentInstruction.Position, currentInstruction.ToolIndex, @@ -162,6 +163,7 @@ namespace MatterHackers.GCodeVisualizer { renderFeaturesForLayer.Add( new RenderFeatureTravel( + instructionIndex, previousInstruction.Position, currentInstruction.Position, currentInstruction.ToolIndex, diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs index 763231a48..fc6686dcf 100644 --- a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs @@ -36,6 +36,10 @@ namespace MatterHackers.GCodeVisualizer { protected int toolIndex; + /// + /// The actual gcode line in the sourc gcode file + /// + public int InstructionIndex { get; private set; } public static Color HighlightColor { get; set; } = new Color("#D0F476"); public static Color StartColor { get; set; } = Color.Red; @@ -46,9 +50,10 @@ namespace MatterHackers.GCodeVisualizer public abstract void CreateRender3DData(VectorPOD colorVertexData, VectorPOD indexData, GCodeRenderInfo renderInfo); - public RenderFeatureBase(int extruderIndex) + public RenderFeatureBase(int instructionIndex, int toolIndex) { - this.toolIndex = extruderIndex; + this.toolIndex = toolIndex; + this.InstructionIndex = instructionIndex; } static public void CreateCylinder(VectorPOD colorVertexData, VectorPOD indexData, Vector3 startPos, Vector3 endPos, double radius, int steps, Color color, double layerHeight) diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs index 02f5148a0..0be7663f5 100644 --- a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs @@ -42,8 +42,8 @@ namespace MatterHackers.GCodeVisualizer private Color color; private Color gray; - public RenderFeatureExtrusion(Vector3 start, Vector3 end, int toolIndex, double travelSpeed, double totalExtrusionMm, double filamentDiameterMm, double layerHeight, Color color, Color gray) - : base(start, end, toolIndex, travelSpeed) + public RenderFeatureExtrusion(int instructionIndex, Vector3 start, Vector3 end, int toolIndex, double travelSpeed, double totalExtrusionMm, double filamentDiameterMm, double layerHeight, Color color, Color gray) + : base(instructionIndex, start, end, toolIndex, travelSpeed) { this.color = color; this.gray = gray; diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs index 689ec4d28..0ac0e0e90 100644 --- a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs @@ -43,8 +43,8 @@ namespace MatterHackers.GCodeVisualizer private float mmPerSecond; private Vector3Float position; - public RenderFeatureRetract(Vector3 position, double extrusionAmount, int extruderIndex, double mmPerSecond) - : base(extruderIndex) + public RenderFeatureRetract(int instructionIndex, Vector3 position, double extrusionAmount, int extruderIndex, double mmPerSecond) + : base(instructionIndex, extruderIndex) { this.extrusionAmount = (float)extrusionAmount; this.mmPerSecond = (float)mmPerSecond; diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs index 764f3ef67..c90587515 100644 --- a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs @@ -45,8 +45,8 @@ namespace MatterHackers.GCodeVisualizer public Vector3Float End => end; - public RenderFeatureTravel(Vector3 start, Vector3 end, int toolIndex, double travelSpeed) - : base(toolIndex) + public RenderFeatureTravel(int instructionIndex, Vector3 start, Vector3 end, int toolIndex, double travelSpeed) + : base(instructionIndex, toolIndex) { this.toolIndex = toolIndex; this.start = new Vector3Float(start); diff --git a/MatterControl.Printing/GCode/GCodeFile.cs b/MatterControl.Printing/GCode/GCodeFile.cs index 46b054209..3dc99a948 100644 --- a/MatterControl.Printing/GCode/GCodeFile.cs +++ b/MatterControl.Printing/GCode/GCodeFile.cs @@ -185,12 +185,13 @@ namespace MatterControl.Printing } else { - return new GCodeMemoryFile(fileName, + return GCodeMemoryFile.Load(fileName, maxAccelerationMmPerS2, maxVelocityMmPerS, velocitySameAsStopMmPerS, speedMultiplier, - cancellationToken); + cancellationToken, + null); } } diff --git a/MatterControl.Printing/GCode/GCodeMemoryFile.cs b/MatterControl.Printing/GCode/GCodeMemoryFile.cs index 81e8227cb..def53fbcd 100644 --- a/MatterControl.Printing/GCode/GCodeMemoryFile.cs +++ b/MatterControl.Printing/GCode/GCodeMemoryFile.cs @@ -40,79 +40,35 @@ namespace MatterControl.Printing { public class GCodeMemoryFile : GCodeFile { - private double parsingLastZ; - private bool gcodeHasExplicitLayerChangeInfo = false; - - private double filamentUsedMmCache = 0; private double diameterOfFilamentUsedMmCache = 0; - - private List layerHeights = new List(); - private List toolChanges = new List(); - private List GCodeCommandQueue = new List(); - + private double filamentDiameterCache = 0; + private double filamentUsedMmCache = 0; private bool foundFirstLayerMarker; + private List GCodeCommandQueue = new List(); + private bool gcodeHasExplicitLayerChangeInfo = false; + private int lastPrintLine; + private List layerHeights = new List(); + private double parsingLastZ; + private List toolChanges = new List(); public GCodeMemoryFile(bool gcodeHasExplicitLayerChangeInfo = false) { this.gcodeHasExplicitLayerChangeInfo = gcodeHasExplicitLayerChangeInfo; } - public GCodeMemoryFile(string pathAndFileName, - Vector4 maxAccelerationMmPerS2, - Vector4 maxVelocityMmPerS, - Vector4 velocitySameAsStopMmPerS, - Vector4 speedMultiplier, - CancellationToken cancellationToken, bool gcodeHasExplicitLayerChangeInfo = false) - { - this.gcodeHasExplicitLayerChangeInfo = gcodeHasExplicitLayerChangeInfo; + public List IndexOfLayerStart { get; private set; } = new List(); - var loadedFile = GCodeMemoryFile.Load(pathAndFileName, - maxAccelerationMmPerS2, - maxVelocityMmPerS, - velocitySameAsStopMmPerS, - speedMultiplier, - cancellationToken, null); - if (loadedFile != null) - { - this.IndexOfLayerStart = loadedFile.IndexOfLayerStart; - this.parsingLastZ = loadedFile.parsingLastZ; - this.GCodeCommandQueue = loadedFile.GCodeCommandQueue; - } - } - - public override PrinterMachineInstruction Instruction(int index) + public override int LayerCount { - return GCodeCommandQueue[index]; + get { return IndexOfLayerStart.Count; } } public override int LineCount => GCodeCommandQueue.Count; - public override void Clear() - { - IndexOfLayerStart.Clear(); - GCodeCommandQueue.Clear(); - } + public HashSet Speeds { get; private set; } public override double TotalSecondsInPrint => Instruction(0).SecondsToEndFromHere; - public void Add(PrinterMachineInstruction printerMachineInstruction) - { - Insert(LineCount, printerMachineInstruction); - } - - public void Insert(int insertIndex, PrinterMachineInstruction printerMachineInstruction) - { - for (int i = 0; i < IndexOfLayerStart.Count; i++) - { - if (insertIndex < IndexOfLayerStart[i]) - { - IndexOfLayerStart[i]++; - } - } - - GCodeCommandQueue.Insert(insertIndex, printerMachineInstruction); - } - public static GCodeMemoryFile Load(Stream fileStream, Vector4 maxAccelerationMmPerS2, Vector4 maxVelocityMmPerS, @@ -170,20 +126,342 @@ namespace MatterControl.Printing return null; } - private static IEnumerable CustomSplit(string newtext, char splitChar) + public void Add(PrinterMachineInstruction printerMachineInstruction) { - int endOfLastFind = 0; - int positionOfSplitChar = newtext.IndexOf(splitChar); - while (positionOfSplitChar != -1) + Insert(LineCount, printerMachineInstruction); + } + + public override void Clear() + { + IndexOfLayerStart.Clear(); + GCodeCommandQueue.Clear(); + } + + public override RectangleDouble GetBounds() + { + RectangleDouble bounds = new RectangleDouble(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue); + foreach (PrinterMachineInstruction state in GCodeCommandQueue) { - string text = newtext.Substring(endOfLastFind, positionOfSplitChar - endOfLastFind).Trim(); - yield return text; - endOfLastFind = positionOfSplitChar + 1; - positionOfSplitChar = newtext.IndexOf(splitChar, endOfLastFind); + bounds.Left = Math.Min(state.Position.X, bounds.Left); + bounds.Right = Math.Max(state.Position.X, bounds.Right); + bounds.Bottom = Math.Min(state.Position.Y, bounds.Bottom); + bounds.Top = Math.Max(state.Position.Y, bounds.Top); } - string lastText = newtext.Substring(endOfLastFind); - yield return lastText; + return bounds; + } + + public override double GetFilamentCubicMm(double filamentDiameterMm) + { + double filamentUsedMm = GetFilamentUsedMm(filamentDiameterMm); + double filamentRadius = filamentDiameterMm / 2; + double areaSquareMm = (filamentRadius * filamentRadius) * Math.PI; + + return areaSquareMm * filamentUsedMm; + } + + public override double GetFilamentDiameter() + { + if (filamentDiameterCache == 0) + { + // check the beginning of the file for the filament diameter + for (int i = 0; i < Math.Min(100, GCodeCommandQueue.Count); i++) + { + if (FindDiameter(i, ref filamentDiameterCache)) + { + break; + } + } + + // check the end of the file for the filament diameter + if (filamentDiameterCache == 0) + { + // didn't find it, so look at the end of the file for filament_diameter = + for (int i = GCodeCommandQueue.Count - 1; i > Math.Max(0, GCodeCommandQueue.Count - 100); i--) + { + if (FindDiameter(i, ref filamentDiameterCache)) + { + break; + } + } + } + + if (filamentDiameterCache == 0) + { + // it is still 0 so set it to something so we render + filamentDiameterCache = 1.75; + } + } + + return filamentDiameterCache; + } + + public override double GetFilamentUsedMm(double filamentDiameter) + { + if (filamentUsedMmCache == 0 || filamentDiameter != diameterOfFilamentUsedMmCache) + { + double lastEPosition = 0; + double filamentMm = 0; + for (int i = 0; i < GCodeCommandQueue.Count; i++) + { + PrinterMachineInstruction instruction = GCodeCommandQueue[i]; + //filamentMm += instruction.EPosition; + + string lineToParse = instruction.Line; + if (lineToParse.StartsWith("G0") || lineToParse.StartsWith("G1")) + { + double ePosition = lastEPosition; + if (GetFirstNumberAfter("E", lineToParse, ref ePosition)) + { + if (instruction.MovementType == PrinterMachineInstruction.MovementTypes.Absolute) + { + double deltaEPosition = ePosition - lastEPosition; + filamentMm += deltaEPosition; + } + else + { + filamentMm += ePosition; + } + + lastEPosition = ePosition; + } + } + else if (lineToParse.StartsWith("G92")) + { + double ePosition = 0; + if (GetFirstNumberAfter("E", lineToParse, ref ePosition)) + { + lastEPosition = ePosition; + } + } + } + + filamentUsedMmCache = filamentMm; + diameterOfFilamentUsedMmCache = filamentDiameter; + } + + return filamentUsedMmCache; + } + + public override double GetFilamentWeightGrams(double filamentDiameterMm, double densityGramsPerCubicCm) + { + double cubicMmPerCubicCm = 1000; + double gramsPerCubicMm = densityGramsPerCubicCm / cubicMmPerCubicCm; + double cubicMms = GetFilamentCubicMm(filamentDiameterMm); + return cubicMms * gramsPerCubicMm; + } + + public override int GetFirstLayerInstruction(int layerIndex) + { + if (layerIndex < IndexOfLayerStart.Count) + { + return IndexOfLayerStart[layerIndex]; + } + + // else return the last instruction + return GCodeCommandQueue.Count - 1; + } + + /// + /// Get the height of the bottom of this layer as measure from the bed + /// + /// + /// + public double GetLayerBottom(int layerIndex) + { + double total = 0; + for (int i = 0; i < layerIndex; i++) + { + total += GetLayerHeight(i); + } + return total; + } + + /// + /// Get the height of this layer (from the top of the previous layer to the top of this layer). + /// + /// + /// + public override double GetLayerHeight(int layerIndex) + { + if (layerHeights.Count > 0) + { + if (layerIndex < layerHeights.Count) + { + return layerHeights[layerIndex]; + } + + return 0; + } + + if (IndexOfLayerStart.Count > 2) + { + return GCodeCommandQueue[IndexOfLayerStart[2]].Z - GCodeCommandQueue[IndexOfLayerStart[1]].Z; + } + + return .5; + } + + public override int GetLayerIndex(int instructionIndex) + { + if (instructionIndex >= 0 + && instructionIndex <= LineCount) + { + for (var i = IndexOfLayerStart.Count - 1; i >= 0; i--) + { + var lineStart = IndexOfLayerStart[i]; + + if (instructionIndex >= lineStart) + { + return i; + } + } + } + + return -1; + } + + /// + /// Get the height of the top of this layer as measure from the bed + /// + /// + /// + public override double GetLayerTop(int layerIndex) + { + double total = 0; + for (int i = 0; i <= layerIndex; i++) + { + total += GetLayerHeight(i); + } + return total; + } + + public override Vector2 GetWeightedCenter() + { + MatterHackers.VectorMath.Vector2 total = new MatterHackers.VectorMath.Vector2(); + foreach (PrinterMachineInstruction state in GCodeCommandQueue) + { + total += new MatterHackers.VectorMath.Vector2(state.Position.X, state.Position.Y); + } + + return total / GCodeCommandQueue.Count; + } + + public void Insert(int insertIndex, PrinterMachineInstruction printerMachineInstruction) + { + for (int i = 0; i < IndexOfLayerStart.Count; i++) + { + if (insertIndex < IndexOfLayerStart[i]) + { + IndexOfLayerStart[i]++; + } + } + + GCodeCommandQueue.Insert(insertIndex, printerMachineInstruction); + } + + public override PrinterMachineInstruction Instruction(int index) + { + return GCodeCommandQueue[index]; + } + + public override bool IsExtruding(int instructionIndexToCheck) + { + if (instructionIndexToCheck > 1 && instructionIndexToCheck < GCodeCommandQueue.Count) + { + double extrusionLength = GCodeCommandQueue[instructionIndexToCheck].EPosition - GCodeCommandQueue[instructionIndexToCheck - 1].EPosition; + if (extrusionLength > 0) + { + return true; + } + } + + return false; + } + + public (int toolIndex, double time) NextToolChange(int instructionIndex, int currentToolIndex = -1) + { + int nextToolChange = -1; + // find the first tool change that we are less than + for (int i = 0; i < toolChanges.Count; i++) + { + if (instructionIndex < toolChanges[i] + && GCodeCommandQueue[toolChanges[i]].ToolIndex != currentToolIndex) + { + nextToolChange = i; + break; + } + } + + if (nextToolChange >= 0) + { + var toolIndex = GCodeCommandQueue[toolChanges[nextToolChange]].ToolIndex; + var time = GCodeCommandQueue[instructionIndex].SecondsToEndFromHere - GCodeCommandQueue[toolChanges[nextToolChange]].SecondsToEndFromHere; + return (toolIndex, time); + } + + // there are no more tool changes + return (currentToolIndex, double.PositiveInfinity); + } + + public override double PercentComplete(int instructionIndex) + { + if (GCodeCommandQueue.Count > 0) + { + return Math.Min(99.9, (double)instructionIndex / (double)GCodeCommandQueue.Count * 100); + } + + return 100; + } + + public override double Ratio0to1IntoContainedLayer(int instructionIndex) + { + int currentLayer = GetLayerIndex(instructionIndex); + + if (currentLayer > -1) + { + int startIndex = IndexOfLayerStart[currentLayer]; + + int endIndex = LineCount - 1; + + if (currentLayer < LayerCount - 1) + { + endIndex = IndexOfLayerStart[currentLayer + 1]; + } + else + { + // Improved last layer percent complete - seek endIndex to 'MatterSlice Completed' line, otherwise leave at LineCount - 1 + if (lastPrintLine == -1) + { + string line = ""; + lastPrintLine = instructionIndex; + do + { + line = GCodeCommandQueue[Math.Min(GCodeCommandQueue.Count - 1, lastPrintLine)].Line; + lastPrintLine++; + } while (line != "; MatterSlice Completed Successfully" + && lastPrintLine < endIndex); + } + + endIndex = lastPrintLine; + } + + int deltaFromStart = Math.Max(0, instructionIndex - startIndex); + return deltaFromStart / (double)(endIndex - startIndex); + } + + return 0; + } + + public void Save(string dest) + { + using (StreamWriter file = new StreamWriter(dest)) + { + foreach (PrinterMachineInstruction instruction in GCodeCommandQueue) + { + file.WriteLine(instruction.Line); + } + } } private static int CountNumLines(string gCodeString) @@ -200,6 +478,22 @@ namespace MatterControl.Printing return crCount + 1; } + private static IEnumerable CustomSplit(string newtext, char splitChar) + { + int endOfLastFind = 0; + int positionOfSplitChar = newtext.IndexOf(splitChar); + while (positionOfSplitChar != -1) + { + string text = newtext.Substring(endOfLastFind, positionOfSplitChar - endOfLastFind).Trim(); + yield return text; + endOfLastFind = positionOfSplitChar + 1; + positionOfSplitChar = newtext.IndexOf(splitChar, endOfLastFind); + } + + string lastText = newtext.Substring(endOfLastFind); + yield return lastText; + } + private static GCodeMemoryFile ParseFileContents(string gCodeString, Vector4 maxAccelerationMmPerS2, Vector4 maxVelocityMmPerS, @@ -288,11 +582,7 @@ namespace MatterControl.Printing break; default: -#if DEBUG - throw new NotImplementedException(); -#else break; -#endif } } @@ -390,7 +680,7 @@ namespace MatterControl.Printing } } - if(instruction.ToolIndex != currentTool) + if (instruction.ToolIndex != currentTool) { toolChanges.Add(lineIndex); currentTool = instruction.ToolIndex; @@ -436,34 +726,160 @@ namespace MatterControl.Printing } } - public override double PercentComplete(int instructionIndex) + private bool FindDiameter(int lineIndex, ref double filamentDiameterCache) { - if (GCodeCommandQueue.Count > 0) + if (GetFirstNumberAfter("filamentDiameter = ", GCodeCommandQueue[lineIndex].Line, ref filamentDiameterCache, 0, "")) { - return Math.Min(99.9, (double)instructionIndex / (double)GCodeCommandQueue.Count * 100); + return true; } - return 100; - } - - public override int GetFirstLayerInstruction(int layerIndex) - { - if (layerIndex < IndexOfLayerStart.Count) + if (GetFirstNumberAfter("; filament_diameter = ", GCodeCommandQueue[lineIndex].Line, ref filamentDiameterCache, 0, "")) { - return IndexOfLayerStart[layerIndex]; + return true; } - // else return the last instruction - return GCodeCommandQueue.Count - 1; + return false; } - public override int LayerCount + private void ParseGLine(string lineString, PrinterMachineInstruction processingMachineState) { - get { return IndexOfLayerStart.Count; } - } + // take off any comments before we check its length + int commentIndex = lineString.IndexOf(';'); + if (commentIndex != -1) + { + lineString = lineString.Substring(0, commentIndex); + } - public HashSet Speeds { get; private set; } - public List IndexOfLayerStart { get; set; } = new List(); + string[] splitOnSpace = lineString.Split(' '); + string onlyNumber = splitOnSpace[0].Substring(1).Trim(); + switch (onlyNumber) + { + case "0": + goto case "1"; + + case "4": + case "04": + // wait a given number of milliseconds + break; + + case "1": + // get the x y z to move to + { + double ePosition = processingMachineState.EPosition; + var position = processingMachineState.Position; + if (processingMachineState.MovementType == PrinterMachineInstruction.MovementTypes.Relative) + { + position = Vector3.Zero; + ePosition = 0; + } + + GCodeFile.GetFirstNumberAfter("X", lineString, ref position.X); + GCodeFile.GetFirstNumberAfter("Y", lineString, ref position.Y); + GCodeFile.GetFirstNumberAfter("Z", lineString, ref position.Z); + GCodeFile.GetFirstNumberAfter("E", lineString, ref ePosition); + + double feedrate = 0; + if (GCodeFile.GetFirstNumberAfter("F", lineString, ref feedrate)) + { + processingMachineState.FeedRate = (float)feedrate; + } + + if (processingMachineState.MovementType == PrinterMachineInstruction.MovementTypes.Absolute) + { + processingMachineState.Position = position; + processingMachineState.EPosition = (float)ePosition; + } + else + { + processingMachineState.Position += position; + processingMachineState.EPosition += (float)ePosition; + } + } + + if (!gcodeHasExplicitLayerChangeInfo) + { + if (processingMachineState.Z != parsingLastZ || IndexOfLayerStart.Count == 0) + { + // if we changed z or there is a movement and we have never started a layer index + IndexOfLayerStart.Add(GCodeCommandQueue.Count); + } + } + parsingLastZ = processingMachineState.Position.Z; + break; + + case "10": // firmware retract + break; + + case "11": // firmware unretract + break; + + case "21": + // set to metric + break; + + case "28": + // G28 Return to home position (machine zero, aka machine reference point) + break; + + case "29": + // G29 Probe the z-bed in 3 places + break; + + case "30": + // G30 Probe z in current position + break; + + case "90": // G90 is Absolute Distance Mode + processingMachineState.MovementType = PrinterMachineInstruction.MovementTypes.Absolute; + break; + + case "91": // G91 is Incremental Distance Mode + processingMachineState.MovementType = PrinterMachineInstruction.MovementTypes.Relative; + break; + + case "92": + { + // set current head position values (used to reset origin) + double value = 0; + if (GCodeFile.GetFirstNumberAfter("X", lineString, ref value)) + { + processingMachineState.PositionSet |= PositionSet.X; + processingMachineState.X = value; + } + if (GCodeFile.GetFirstNumberAfter("Y", lineString, ref value)) + { + processingMachineState.PositionSet |= PositionSet.Y; + processingMachineState.Y = value; + } + if (GCodeFile.GetFirstNumberAfter("Z", lineString, ref value)) + { + processingMachineState.PositionSet |= PositionSet.Z; + processingMachineState.Z = value; + } + if (GCodeFile.GetFirstNumberAfter("E", lineString, ref value)) + { + processingMachineState.PositionSet |= PositionSet.E; + processingMachineState.EPosition = (float)value; + } + } + break; + + case "130": + //Set Digital Potentiometer value + break; + + case "161": + // home x,y axis minimum + break; + + case "162": + // home z axis maximum + break; + + default: + break; + } + } private void ParseMLine(string lineString, PrinterMachineInstruction processingMachineState) { @@ -629,10 +1045,13 @@ namespace MatterControl.Printing case "565": // M565: Set Z probe offset break; + case "1200"://M1200 Makerbot Fake gCode command for start build notification break; + case "1201"://M1201 Makerbot Fake gCode command for end build notification break; + case "1202"://M1202 Makerbot Fake gCode command for reset board break; @@ -640,427 +1059,5 @@ namespace MatterControl.Printing break; } } - - private void ParseGLine(string lineString, PrinterMachineInstruction processingMachineState) - { - // take off any comments before we check its length - int commentIndex = lineString.IndexOf(';'); - if (commentIndex != -1) - { - lineString = lineString.Substring(0, commentIndex); - } - - string[] splitOnSpace = lineString.Split(' '); - string onlyNumber = splitOnSpace[0].Substring(1).Trim(); - switch (onlyNumber) - { - case "0": - goto case "1"; - - case "4": - case "04": - // wait a given number of milliseconds - break; - - case "1": - // get the x y z to move to - { - double ePosition = processingMachineState.EPosition; - var position = processingMachineState.Position; - if (processingMachineState.MovementType == PrinterMachineInstruction.MovementTypes.Relative) - { - position = Vector3.Zero; - ePosition = 0; - } - - GCodeFile.GetFirstNumberAfter("X", lineString, ref position.X); - GCodeFile.GetFirstNumberAfter("Y", lineString, ref position.Y); - GCodeFile.GetFirstNumberAfter("Z", lineString, ref position.Z); - GCodeFile.GetFirstNumberAfter("E", lineString, ref ePosition); - - double feedrate = 0; - if (GCodeFile.GetFirstNumberAfter("F", lineString, ref feedrate)) - { - processingMachineState.FeedRate = (float)feedrate; - } - - if (processingMachineState.MovementType == PrinterMachineInstruction.MovementTypes.Absolute) - { - processingMachineState.Position = position; - processingMachineState.EPosition = (float)ePosition; - } - else - { - processingMachineState.Position += position; - processingMachineState.EPosition += (float)ePosition; - } - } - - if (!gcodeHasExplicitLayerChangeInfo) - { - if (processingMachineState.Z != parsingLastZ || IndexOfLayerStart.Count == 0) - { - // if we changed z or there is a movement and we have never started a layer index - IndexOfLayerStart.Add(GCodeCommandQueue.Count); - } - } - parsingLastZ = processingMachineState.Position.Z; - break; - - case "10": // firmware retract - break; - - case "11": // firmware unretract - break; - - case "21": - // set to metric - break; - - case "28": - // G28 Return to home position (machine zero, aka machine reference point) - break; - - case "29": - // G29 Probe the z-bed in 3 places - break; - - case "30": - // G30 Probe z in current position - break; - - case "90": // G90 is Absolute Distance Mode - processingMachineState.MovementType = PrinterMachineInstruction.MovementTypes.Absolute; - break; - - case "91": // G91 is Incremental Distance Mode - processingMachineState.MovementType = PrinterMachineInstruction.MovementTypes.Relative; - break; - - case "92": - { - // set current head position values (used to reset origin) - double value = 0; - if (GCodeFile.GetFirstNumberAfter("X", lineString, ref value)) - { - processingMachineState.PositionSet |= PositionSet.X; - processingMachineState.X = value; - } - if (GCodeFile.GetFirstNumberAfter("Y", lineString, ref value)) - { - processingMachineState.PositionSet |= PositionSet.Y; - processingMachineState.Y = value; - } - if (GCodeFile.GetFirstNumberAfter("Z", lineString, ref value)) - { - processingMachineState.PositionSet |= PositionSet.Z; - processingMachineState.Z = value; - } - if (GCodeFile.GetFirstNumberAfter("E", lineString, ref value)) - { - processingMachineState.PositionSet |= PositionSet.E; - processingMachineState.EPosition = (float)value; - } - } - break; - - case "130": - //Set Digital Potentiometer value - break; - - case "161": - // home x,y axis minimum - break; - - case "162": - // home z axis maximum - break; - - default: - break; - } - } - - public override Vector2 GetWeightedCenter() - { - MatterHackers.VectorMath.Vector2 total = new MatterHackers.VectorMath.Vector2(); - foreach (PrinterMachineInstruction state in GCodeCommandQueue) - { - total += new MatterHackers.VectorMath.Vector2(state.Position.X, state.Position.Y); - } - - return total / GCodeCommandQueue.Count; - } - - public override RectangleDouble GetBounds() - { - RectangleDouble bounds = new RectangleDouble(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue); - foreach (PrinterMachineInstruction state in GCodeCommandQueue) - { - bounds.Left = Math.Min(state.Position.X, bounds.Left); - bounds.Right = Math.Max(state.Position.X, bounds.Right); - bounds.Bottom = Math.Min(state.Position.Y, bounds.Bottom); - bounds.Top = Math.Max(state.Position.Y, bounds.Top); - } - - return bounds; - } - - public override bool IsExtruding(int instructionIndexToCheck) - { - if (instructionIndexToCheck > 1 && instructionIndexToCheck < GCodeCommandQueue.Count) - { - double extrusionLength = GCodeCommandQueue[instructionIndexToCheck].EPosition - GCodeCommandQueue[instructionIndexToCheck - 1].EPosition; - if (extrusionLength > 0) - { - return true; - } - } - - return false; - } - - public override double GetFilamentUsedMm(double filamentDiameter) - { - if (filamentUsedMmCache == 0 || filamentDiameter != diameterOfFilamentUsedMmCache) - { - double lastEPosition = 0; - double filamentMm = 0; - for (int i = 0; i < GCodeCommandQueue.Count; i++) - { - PrinterMachineInstruction instruction = GCodeCommandQueue[i]; - //filamentMm += instruction.EPosition; - - string lineToParse = instruction.Line; - if (lineToParse.StartsWith("G0") || lineToParse.StartsWith("G1")) - { - double ePosition = lastEPosition; - if (GetFirstNumberAfter("E", lineToParse, ref ePosition)) - { - if (instruction.MovementType == PrinterMachineInstruction.MovementTypes.Absolute) - { - double deltaEPosition = ePosition - lastEPosition; - filamentMm += deltaEPosition; - } - else - { - filamentMm += ePosition; - } - - lastEPosition = ePosition; - } - } - else if (lineToParse.StartsWith("G92")) - { - double ePosition = 0; - if (GetFirstNumberAfter("E", lineToParse, ref ePosition)) - { - lastEPosition = ePosition; - } - } - } - - filamentUsedMmCache = filamentMm; - diameterOfFilamentUsedMmCache = filamentDiameter; - } - - return filamentUsedMmCache; - } - - public override double GetFilamentCubicMm(double filamentDiameterMm) - { - double filamentUsedMm = GetFilamentUsedMm(filamentDiameterMm); - double filamentRadius = filamentDiameterMm / 2; - double areaSquareMm = (filamentRadius * filamentRadius) * Math.PI; - - return areaSquareMm * filamentUsedMm; - } - - public override double GetFilamentWeightGrams(double filamentDiameterMm, double densityGramsPerCubicCm) - { - double cubicMmPerCubicCm = 1000; - double gramsPerCubicMm = densityGramsPerCubicCm / cubicMmPerCubicCm; - double cubicMms = GetFilamentCubicMm(filamentDiameterMm); - return cubicMms * gramsPerCubicMm; - } - - public void Save(string dest) - { - using (StreamWriter file = new StreamWriter(dest)) - { - foreach (PrinterMachineInstruction instruction in GCodeCommandQueue) - { - file.WriteLine(instruction.Line); - } - } - } - - double filamentDiameterCache = 0; - public override double GetFilamentDiameter() - { - if (filamentDiameterCache == 0) - { - // check the beginning of the file for the filament diameter - for (int i = 0; i < Math.Min(100, GCodeCommandQueue.Count); i++) - { - if (FindDiameter(i, ref filamentDiameterCache)) - { - break; - } - } - - // check the end of the file for the filament diameter - if (filamentDiameterCache == 0) - { - // didn't find it, so look at the end of the file for filament_diameter = - for (int i = GCodeCommandQueue.Count - 1; i > Math.Max(0, GCodeCommandQueue.Count - 100); i--) - { - if (FindDiameter(i, ref filamentDiameterCache)) - { - break; - } - } - } - - if (filamentDiameterCache == 0) - { - // it is still 0 so set it to something so we render - filamentDiameterCache = 1.75; - } - } - - return filamentDiameterCache; - } - - private bool FindDiameter(int lineIndex, ref double filamentDiameterCache) - { - if (GetFirstNumberAfter("filamentDiameter = ", GCodeCommandQueue[lineIndex].Line, ref filamentDiameterCache, 0, "")) - { - return true; - } - - if (GetFirstNumberAfter("; filament_diameter = ", GCodeCommandQueue[lineIndex].Line, ref filamentDiameterCache, 0, "")) - { - return true; - } - - return false; - } - - /// - /// Get the height of this layer (from the top of the previous layer to the top of this layer). - /// - /// - /// - public override double GetLayerHeight(int layerIndex) - { - if (layerHeights.Count > 0) - { - if (layerIndex < layerHeights.Count) - { - return layerHeights[layerIndex]; - } - - return 0; - } - - if (IndexOfLayerStart.Count > 2) - { - return GCodeCommandQueue[IndexOfLayerStart[2]].Z - GCodeCommandQueue[IndexOfLayerStart[1]].Z; - } - - return .5; - } - - /// - /// Get the height of the top of this layer as measure from the bed - /// - /// - /// - public override double GetLayerTop(int layerIndex) - { - double total = 0; - for (int i = 0; i <= layerIndex; i++) - { - total += GetLayerHeight(i); - } - return total; - } - - /// - /// Get the height of the bottom of this layer as measure from the bed - /// - /// - /// - public double GetLayerBottom(int layerIndex) - { - double total = 0; - for (int i = 0; i < layerIndex; i++) - { - total += GetLayerHeight(i); - } - return total; - } - - public override int GetLayerIndex(int instructionIndex) - { - if (instructionIndex >= 0 - && instructionIndex <= LineCount) - { - for (var i = IndexOfLayerStart.Count - 1; i >= 0; i--) - { - var lineStart = IndexOfLayerStart[i]; - - if (instructionIndex >= lineStart) - { - return i; - } - } - } - - return -1; - } - - private static int lastPrintLine = -1; - - public override double Ratio0to1IntoContainedLayer(int instructionIndex) - { - int currentLayer = GetLayerIndex(instructionIndex); - - if (currentLayer > -1) - { - int startIndex = IndexOfLayerStart[currentLayer]; - - int endIndex = LineCount - 1; - - if (currentLayer < LayerCount - 1) - { - endIndex = IndexOfLayerStart[currentLayer + 1]; - } - else - { - // Improved last layer percent complete - seek endIndex to 'MatterSlice Completed' line, otherwise leave at LineCount - 1 - if (lastPrintLine == -1) - { - string line = ""; - lastPrintLine = instructionIndex; - do - { - line = GCodeCommandQueue[Math.Min(GCodeCommandQueue.Count - 1, lastPrintLine)].Line; - lastPrintLine++; - - } while (line != "; MatterSlice Completed Successfully" - && lastPrintLine < endIndex); - } - - endIndex = lastPrintLine; - } - - int deltaFromStart = Math.Max(0, instructionIndex - startIndex); - return deltaFromStart / (double)(endIndex - startIndex); - } - - return 0; - } } } \ No newline at end of file diff --git a/MatterControlLib/Library/Export/GCodeExport.cs b/MatterControlLib/Library/Export/GCodeExport.cs index 408b4e38e..0e3500bce 100644 --- a/MatterControlLib/Library/Export/GCodeExport.cs +++ b/MatterControlLib/Library/Export/GCodeExport.cs @@ -282,12 +282,13 @@ namespace MatterHackers.MatterControl.Library.Export public static GCodeStream GetExportStream(PrinterConfig printer, GCodeStream gCodeBaseStream, bool applyLeveling) { - var queueStream = new QueuedCommandsStream(printer, gCodeBaseStream); - GCodeStream accumulatedStream = queueStream; + var queuedCommandStream = new QueuedCommandsStream(printer, gCodeBaseStream); + GCodeStream accumulatedStream = queuedCommandStream; if (printer.Settings.GetValue(SettingsKey.extruder_count) > 1) { - accumulatedStream = new ToolChangeStream(printer, accumulatedStream, queueStream); + accumulatedStream = new ToolChangeStream(printer, accumulatedStream, queuedCommandStream); + accumulatedStream = new HotendTemperatureStream(printer, accumulatedStream, queuedCommandStream); } accumulatedStream = new RelativeToAbsoluteStream(printer, accumulatedStream); @@ -312,7 +313,7 @@ namespace MatterHackers.MatterControl.Library.Export } // this is added to ensure we are rewriting the G0 G1 commands as needed - accumulatedStream = new ProcessWriteRegexStream(printer, accumulatedStream, queueStream); + accumulatedStream = new ProcessWriteRegexStream(printer, accumulatedStream, queuedCommandStream); return accumulatedStream; } diff --git a/MatterControlLib/PartPreviewWindow/GCodeDetails/GCodeDebugView.cs b/MatterControlLib/PartPreviewWindow/GCodeDetails/GCodeDebugView.cs index de3429741..a0f30aea6 100644 --- a/MatterControlLib/PartPreviewWindow/GCodeDetails/GCodeDebugView.cs +++ b/MatterControlLib/PartPreviewWindow/GCodeDetails/GCodeDebugView.cs @@ -38,8 +38,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { public class GCodeDebugView : GCodeDetailsPanel { + private GCodeFile gCodeFile; private PrinterTabPage printerTabPage; private ISceneContext sceneContext; + private GCodeMemoryFile gCodeMemoryFile; private TextWidget startPointWidget; private TextWidget endPointWidget; private TextWidget slopeWidget; @@ -47,20 +49,31 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private TextWidget yInterceptWidget; private TextWidget xInterceptWidget; private TextWidget timeToToolChange; + private TextWidget rawLine; - public GCodeDebugView(PrinterTabPage printerTabPage, GCodeFile gCodeMemoryFile, ISceneContext sceneContext, ThemeConfig theme) + public GCodeDebugView(PrinterTabPage printerTabPage, GCodeFile gCodeFile, ISceneContext sceneContext, ThemeConfig theme) : base(theme) { + this.gCodeFile = gCodeFile; this.printerTabPage = printerTabPage; this.sceneContext = sceneContext; + gCodeMemoryFile = gCodeFile as GCodeMemoryFile; + if (gCodeMemoryFile != null) + { + rawLine = this.AddSetting("G-Code Line".Localize(), ""); + } + startPointWidget = this.AddSetting("Start".Localize(), ""); endPointWidget = this.AddSetting("End".Localize(), ""); lengthWidget = this.AddSetting("Length".Localize(), ""); - slopeWidget = this.AddSetting("Slope".Localize(), ""); - yInterceptWidget = this.AddSetting("Y Intercept".Localize(), ""); - xInterceptWidget = this.AddSetting("X Intercept".Localize(), ""); - timeToToolChange = this.AddSetting("Tool Change".Localize(), ""); + //slopeWidget = this.AddSetting("Slope".Localize(), ""); + //yInterceptWidget = this.AddSetting("Y Intercept".Localize(), ""); + //xInterceptWidget = this.AddSetting("X Intercept".Localize(), ""); + if (gCodeMemoryFile != null) + { + timeToToolChange = this.AddSetting("Time to Tool Change".Localize(), ""); + } // Register listeners printerTabPage.LayerFeaturesScrollbar.SecondValueChanged += this.LayerFeaturesScrollbar_SecondValueChanged; @@ -85,6 +98,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow if (sceneContext.GCodeRenderer[layerIndex, activeFeatureIndex] is RenderFeatureTravel line) { + if (rawLine != null) + { + rawLine.Text = gCodeMemoryFile.Instruction(line.InstructionIndex).Line; + } + var start = line.Start; var end = line.End; @@ -101,20 +119,37 @@ namespace MatterHackers.MatterControl.PartPreviewWindow // n = -x_1 * (y_2 - y_1) / (x_2 - x_1) + y_1 var slope = (end.Y - start.Y) / (end.X - start.X); - slopeWidget.Text = $"{slope:0.###}"; + if (slopeWidget != null) + { + slopeWidget.Text = $"{slope:0.###}"; + } - // -x_1 * (y_2 - y_1) / (x_2 - x_1) + y_1 - var yIntercept = -start.X * slope + start.Y; - yInterceptWidget.Text = $"{yIntercept:0.###}"; + if (yInterceptWidget != null) + { + // -x_1 * (y_2 - y_1) / (x_2 - x_1) + y_1 + var yIntercept = -start.X * slope + start.Y; + yInterceptWidget.Text = $"{yIntercept:0.###}"; + } - // x_1 - y_1*(x_2-x_1)/(y_2-y_1) - var xIntercept = start.X - start.Y * (end.X - start.X) / (end.Y - start.Y); - xInterceptWidget.Text = $"{xIntercept:0.###}"; + if (xInterceptWidget != null) + { + // x_1 - y_1*(x_2-x_1)/(y_2-y_1) + var xIntercept = start.X - start.Y * (end.X - start.X) / (end.Y - start.Y); + xInterceptWidget.Text = $"{xIntercept:0.###}"; + } // put in the time until the next tool change - if(timeToToolChange != null) + if (timeToToolChange != null) { - + var toolChange = gCodeMemoryFile.NextToolChange(line.InstructionIndex); + if (toolChange.time < double.PositiveInfinity) + { + timeToToolChange.Text = $"T{toolChange.toolIndex} : {toolChange.time:0.00}s"; + } + else + { + timeToToolChange.Text = $"No More Changes"; + } } } } diff --git a/MatterControlLib/PrinterCommunication/Io/HotendTemperatureStream.cs b/MatterControlLib/PrinterCommunication/Io/HotendTemperatureStream.cs new file mode 100644 index 000000000..11d834750 --- /dev/null +++ b/MatterControlLib/PrinterCommunication/Io/HotendTemperatureStream.cs @@ -0,0 +1,121 @@ +/* +Copyright (c) 2019, Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterControl.Printing; +using MatterHackers.MatterControl.SlicerConfiguration; + +namespace MatterHackers.MatterControl.PrinterCommunication.Io +{ + public class HotendTemperatureStream : GCodeStreamProxy + { + private bool haveSeenMultipleExtruders; + private int extruderIndex; + private QueuedCommandsStream queuedCommandsStream; + int extruderCount = 0; + + public HotendTemperatureStream(PrinterConfig printer, GCodeStream internalStream, QueuedCommandsStream queuedCommandsStream) + : base(printer, internalStream) + { + this.queuedCommandsStream = queuedCommandsStream; + extruderCount = printer.Settings.GetValue(SettingsKey.extruder_count); + extruderIndex = printer.Connection.ActiveExtruderIndex; + } + + public override string DebugInfo + { + get + { + return $""; + } + } + + public override string ReadLine() + { + string lineToSend = base.ReadLine(); + + if (lineToSend != null + && lineToSend.EndsWith("; NO_PROCESSING")) + { + return lineToSend; + } + + TrackExtruderState(lineToSend); + + // when we are actively printing manage the extruder temperature + if (haveSeenMultipleExtruders + && printer.Connection.Printing) + { + // get the time to the next extruder switch + var toolChange = printer.Connection.NextToolChange(); + + // if we do not switch again + if (toolChange.time == double.PositiveInfinity) + { + // we do not switch extruders again, turn off any that are not currently printing + for (int i = 0; i < extruderCount; i++) + { + if(i != extruderIndex) + { + printer.Connection.SetTargetHotendTemperature(i, 0, true); + } + } + } + + // don't keep checking if need to turn off extruders + haveSeenMultipleExtruders = false; + } + + return lineToSend; + } + + private void TrackExtruderState(string line) + { + if (line == null) + { + return; + } + + if (line.StartsWith("G28)")) + { + extruderIndex = 0; + } + + if (line.StartsWith("T")) + { + var newExtruder = extruderIndex; + GCodeFile.GetFirstNumberAfter("T", line, ref newExtruder); + if(newExtruder != extruderIndex) + { + haveSeenMultipleExtruders = true; + extruderIndex = newExtruder; + } + } + } + } +} \ No newline at end of file diff --git a/MatterControlLib/PrinterCommunication/PrinterConnection.cs b/MatterControlLib/PrinterCommunication/PrinterConnection.cs index 9126e3c3a..624962e8f 100644 --- a/MatterControlLib/PrinterCommunication/PrinterConnection.cs +++ b/MatterControlLib/PrinterCommunication/PrinterConnection.cs @@ -322,6 +322,20 @@ namespace MatterHackers.MatterControl.PrinterCommunication }); } + /// + /// seconds until the next tool change while printing + /// + /// + public (int toolIndex, double time) NextToolChange() + { + if (gCodeFileSwitcher.GCodeFile is GCodeMemoryFile gCodeMemoryFile) + { + return gCodeMemoryFile.NextToolChange(gCodeFileSwitcher.LineIndex); + } + + return (0, 0); + } + private void ExtruderIndexSet(string line) { double extruderBeingSet = 0; @@ -2159,6 +2173,7 @@ You will then need to logout and log back in to the computer for the changes to if (ExtruderCount > 1) { accumulatedStream = new ToolChangeStream(Printer, accumulatedStream, queuedCommandStream); + accumulatedStream = new HotendTemperatureStream(Printer, accumulatedStream, queuedCommandStream); } accumulatedStream = new BabyStepsStream(Printer, accumulatedStream);