diff --git a/MatterControl.OpenGL/GCodeRenderer/ColorVertexData.cs b/MatterControl.OpenGL/GCodeRenderer/ColorVertexData.cs new file mode 100644 index 000000000..83e9c7b2d --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/ColorVertexData.cs @@ -0,0 +1,71 @@ +using MatterHackers.Agg; +using MatterHackers.VectorMath; + +/* +Copyright (c) 2014, Lars Brubaker +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 System.Runtime.InteropServices; + +namespace MatterHackers.GCodeVisualizer +{ + [StructLayout(LayoutKind.Sequential)] + public struct ColorVertexData + { + public byte r; + public byte g; + public byte b; + public byte a; + + public float normalX; + public float normalY; + public float normalZ; + + public float positionX; + public float positionY; + public float positionZ; + + public static readonly int Stride = Marshal.SizeOf(default(ColorVertexData)); + + public ColorVertexData(Vector3 position, Vector3 normal, RGBA_Bytes color) + { + r = (byte)color.Red0To255; + g = (byte)color.Green0To255; + b = (byte)color.Blue0To255; + a = (byte)color.Alpha0To255; + + normalX = (float)normal.x; + normalY = (float)normal.y; + normalZ = (float)normal.z; + + positionX = (float)position.x; + positionY = (float)position.y; + positionZ = (float)position.z; + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/ExtrusionColors.cs b/MatterControl.OpenGL/GCodeRenderer/ExtrusionColors.cs new file mode 100644 index 000000000..ba360e933 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/ExtrusionColors.cs @@ -0,0 +1,76 @@ +using MatterHackers.Agg; + +/* +Copyright (c) 2014, Lars Brubaker +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 System.Collections.Generic; +using System.Linq; + +namespace MatterHackers.GCodeVisualizer +{ + public class ExtrusionColors + { + private SortedList speedColorLookup = new SortedList(); + + public RGBA_Bytes GetColorForSpeed(float speed) + { + if (speed > 0) + { + lock(speedColorLookup) + { + double startColor = 223.0 / 360.0; + double endColor = 5.0 / 360.0; + double delta = startColor - endColor; + + if (!speedColorLookup.ContainsKey(speed)) + { + RGBA_Bytes color = RGBA_Floats.FromHSL(startColor, .99, .49).GetAsRGBA_Bytes(); + speedColorLookup.Add(speed, color); + + if (speedColorLookup.Count > 1) + { + double step = delta / (speedColorLookup.Count - 1); + for (int index = 0; index < speedColorLookup.Count; index++) + { + double offset = step * index; + double fixedColor = startColor - offset; + KeyValuePair keyValue = speedColorLookup.ElementAt(index); + speedColorLookup[keyValue.Key] = RGBA_Floats.FromHSL(fixedColor, .99, .49).GetAsRGBA_Bytes(); + } + } + } + + return speedColorLookup[speed]; + } + } + + return RGBA_Bytes.Black; + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/GCodeRenderInfo.cs b/MatterControl.OpenGL/GCodeRenderer/GCodeRenderInfo.cs new file mode 100644 index 000000000..387ba9271 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/GCodeRenderInfo.cs @@ -0,0 +1,103 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 System; +using MatterHackers.Agg; +using MatterHackers.Agg.Transform; +using MatterHackers.VectorMath; + +namespace MatterHackers.GCodeVisualizer +{ + public class GCodeRenderInfo + { + private Vector2[] extruderOffsets; + + public Vector2 GetExtruderOffset(int index) + { + if (extruderOffsets != null + && extruderOffsets.Length > index) + { + return extruderOffsets[index]; + } + + return Vector2.Zero; + } + + public Func GetMaterialColor { get; } + + public int startLayerIndex; + + public int StartLayerIndex { get { return startLayerIndex; } } + + private int endLayerIndex; + + public int EndLayerIndex { get { return endLayerIndex; } } + + private Affine transform; + + public Affine Transform { get { return transform; } } + + private double layerScale; + + public double LayerScale { get { return layerScale; } } + + private RenderType currentRenderType; + + public RenderType CurrentRenderType { get { return currentRenderType; } } + + private double featureToStartOnRatio0To1; + + public double FeatureToStartOnRatio0To1 { get { return featureToStartOnRatio0To1; } } + + private double featureToEndOnRatio0To1; + + public double FeatureToEndOnRatio0To1 { get { return featureToEndOnRatio0To1; } } + + public GCodeRenderInfo() + { + } + + public GCodeRenderInfo(int startLayerIndex, int endLayerIndex, + Affine transform, double layerScale, RenderType renderType, + double featureToStartOnRatio0To1, double featureToEndOnRatio0To1, + Vector2[] extruderOffsets, + Func getMaterialColor) + { + this.GetMaterialColor = getMaterialColor; + this.startLayerIndex = startLayerIndex; + this.endLayerIndex = endLayerIndex; + this.transform = transform; + this.layerScale = layerScale; + this.currentRenderType = renderType; + this.featureToStartOnRatio0To1 = featureToStartOnRatio0To1; + this.featureToEndOnRatio0To1 = featureToEndOnRatio0To1; + this.extruderOffsets = extruderOffsets; + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs b/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs new file mode 100644 index 000000000..8bafd1714 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/GCodeRenderer.cs @@ -0,0 +1,406 @@ +/* +Copyright (c) 2017, 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 System; +using System.Collections.Generic; +using MatterHackers.Agg; +using MatterHackers.RenderOpenGl; +using MatterHackers.RenderOpenGl.OpenGl; + +namespace MatterHackers.GCodeVisualizer +{ + [Flags] + public enum RenderType + { + None = 0, + Extrusions = 1, + Moves = 2, + Retractions = 4, + SpeedColors = 8, + SimulateExtrusion = 16, + HideExtruderOffsets = 32, + TransparentExtrusion = 64, + }; + + public class GCodeRenderer : IDisposable + { + public static double ExtruderWidth { get; set; } = .4; + + private List> featureStartIndex = new List>(); + private List> featureEndIndex = new List>(); + private List> renderFeatures = new List>(); + + public static RGBA_Bytes ExtrusionColor = RGBA_Bytes.White; + public static RGBA_Bytes TravelColor = RGBA_Bytes.Green; + + private GCodeFile gCodeFileToDraw; + + public GCodeFile GCodeFileToDraw { get { return gCodeFileToDraw; } } + + private ExtrusionColors extrusionColors; + + public GCodeRenderer(GCodeFile gCodeFileToDraw) + { + if (gCodeFileToDraw != null) + { + this.gCodeFileToDraw = gCodeFileToDraw; + + for (int i = 0; i < gCodeFileToDraw.NumChangesInZ; i++) + { + renderFeatures.Add(new List()); + } + } + } + + public void CreateFeaturesForLayerIfRequired(int layerToCreate) + { + if (extrusionColors == null + && gCodeFileToDraw != null + && gCodeFileToDraw.LineCount > 0) + { + extrusionColors = new ExtrusionColors(); + HashSet speeds = new HashSet(); + PrinterMachineInstruction prevInstruction = gCodeFileToDraw.Instruction(0); + for (int i = 1; i < gCodeFileToDraw.LineCount; i++) + { + PrinterMachineInstruction instruction = gCodeFileToDraw.Instruction(i); + if (instruction.EPosition > prevInstruction.EPosition && (instruction.Line.IndexOf('X') != -1 || instruction.Line.IndexOf('Y') != -1)) + { + speeds.Add((float)instruction.FeedRate); + } + + prevInstruction = instruction; + } + + foreach (float speed in speeds) + { + extrusionColors.GetColorForSpeed(speed); + } + } + + if (renderFeatures.Count == 0 + || renderFeatures[layerToCreate].Count > 0) + { + return; + } + + List renderFeaturesForLayer = renderFeatures[layerToCreate]; + + int startRenderIndex = gCodeFileToDraw.GetInstructionIndexAtLayer(layerToCreate); + int endRenderIndex = gCodeFileToDraw.LineCount - 1; + if (layerToCreate < gCodeFileToDraw.NumChangesInZ - 1) + { + endRenderIndex = gCodeFileToDraw.GetInstructionIndexAtLayer(layerToCreate + 1); + } + + for (int instructionIndex = startRenderIndex; instructionIndex < endRenderIndex; instructionIndex++) + { + PrinterMachineInstruction currentInstruction = gCodeFileToDraw.Instruction(instructionIndex); + PrinterMachineInstruction previousInstruction = currentInstruction; + if (instructionIndex > 0) + { + previousInstruction = gCodeFileToDraw.Instruction(instructionIndex - 1); + } + + if (currentInstruction.Position == previousInstruction.Position) + { + if (Math.Abs(currentInstruction.EPosition - previousInstruction.EPosition) > 0) + { + // this is a retraction + renderFeaturesForLayer.Add(new RenderFeatureRetract(currentInstruction.Position, currentInstruction.EPosition - previousInstruction.EPosition, currentInstruction.ExtruderIndex, currentInstruction.FeedRate)); + } + if (currentInstruction.Line.StartsWith("G10")) + { + renderFeaturesForLayer.Add(new RenderFeatureRetract(currentInstruction.Position, -1, currentInstruction.ExtruderIndex, currentInstruction.FeedRate)); + } + else if (currentInstruction.Line.StartsWith("G11")) + { + renderFeaturesForLayer.Add(new RenderFeatureRetract(currentInstruction.Position, 1, currentInstruction.ExtruderIndex, currentInstruction.FeedRate)); + } + } + else + { + if (gCodeFileToDraw.IsExtruding(instructionIndex)) + { + double layerThickness = gCodeFileToDraw.GetLayerHeight(); + if (layerToCreate == 0) + { + layerThickness = gCodeFileToDraw.GetFirstLayerHeight(); + } + + RGBA_Bytes extrusionColor = extrusionColors.GetColorForSpeed((float)currentInstruction.FeedRate); + renderFeaturesForLayer.Add(new RenderFeatureExtrusion(previousInstruction.Position, currentInstruction.Position, currentInstruction.ExtruderIndex, currentInstruction.FeedRate, currentInstruction.EPosition - previousInstruction.EPosition, gCodeFileToDraw.GetFilamentDiameter(), layerThickness, extrusionColor)); + } + else + { + renderFeaturesForLayer.Add(new RenderFeatureTravel(previousInstruction.Position, currentInstruction.Position, currentInstruction.ExtruderIndex, currentInstruction.FeedRate)); + } + } + } + } + + public int GetNumFeatures(int layerToCountFeaturesOn) + { + CreateFeaturesForLayerIfRequired(layerToCountFeaturesOn); + return renderFeatures[layerToCountFeaturesOn].Count; + } + + public void Render(Graphics2D graphics2D, GCodeRenderInfo renderInfo) + { + if (renderFeatures.Count > 0) + { + CreateFeaturesForLayerIfRequired(renderInfo.EndLayerIndex); + + int featuresOnLayer = renderFeatures[renderInfo.EndLayerIndex].Count; + int endFeature = (int)(featuresOnLayer * renderInfo.FeatureToEndOnRatio0To1 + .5); + endFeature = Math.Max(0, Math.Min(endFeature, featuresOnLayer)); + + int startFeature = (int)(featuresOnLayer * renderInfo.FeatureToStartOnRatio0To1 + .5); + startFeature = Math.Max(0, Math.Min(startFeature, featuresOnLayer)); + + // try to make sure we always draw at least one feature + if (endFeature <= startFeature) + { + endFeature = Math.Min(startFeature + 1, featuresOnLayer); + } + if (startFeature >= endFeature) + { + // This can only happen if the start and end are set to the last feature + // Try to set the start feature to one from the end + startFeature = Math.Max(endFeature - 1, 0); + } + + Graphics2DOpenGL graphics2DGl = graphics2D as Graphics2DOpenGL; + if (graphics2DGl != null) + { + graphics2DGl.PreRender(); + GL.Begin(BeginMode.Triangles); + for (int i = startFeature; i < endFeature; i++) + { + RenderFeatureBase feature = renderFeatures[renderInfo.EndLayerIndex][i]; + if (feature != null) + { + feature.Render(graphics2DGl, renderInfo); + } + } + GL.End(); + graphics2DGl.PopOrthoProjection(); + } + else + { + for (int i = startFeature; i < endFeature; i++) + { + RenderFeatureBase feature = renderFeatures[renderInfo.EndLayerIndex][i]; + if (feature != null) + { + feature.Render(graphics2D, renderInfo); + } + } + } + } + } + + private void Create3DDataForLayer(int layerIndex, + VectorPOD colorVertexData, + VectorPOD vertexIndexArray, + GCodeRenderInfo renderInfo) + { + colorVertexData.Clear(); + vertexIndexArray.Clear(); + featureStartIndex[layerIndex].Clear(); + featureEndIndex[layerIndex].Clear(); + + for (int i = 0; i < renderFeatures[layerIndex].Count; i++) + { + featureStartIndex[layerIndex].Add(vertexIndexArray.Count); + RenderFeatureBase feature = renderFeatures[layerIndex][i]; + if (feature != null) + { + feature.CreateRender3DData(colorVertexData, vertexIndexArray, renderInfo); + } + featureEndIndex[layerIndex].Add(vertexIndexArray.Count); + } + } + + public void Dispose() + { + Clear3DGCode(); + } + + public void Clear3DGCode() + { + if (layerVertexBuffer != null) + { + for (int i = 0; i < layerVertexBuffer.Count; i++) + { + if (layerVertexBuffer[i] != null) + { + layerVertexBuffer[i].Dispose(); + layerVertexBuffer[i] = null; + } + } + } + } + + private List layerVertexBuffer; + private RenderType lastRenderType = RenderType.None; + + private static readonly bool Is32Bit = IntPtr.Size == 4; + + public void Render3D(GCodeRenderInfo renderInfo) + { + if (layerVertexBuffer == null) + { + layerVertexBuffer = new List(); + layerVertexBuffer.Capacity = gCodeFileToDraw.NumChangesInZ; + for (int layerIndex = 0; layerIndex < gCodeFileToDraw.NumChangesInZ; layerIndex++) + { + layerVertexBuffer.Add(null); + featureStartIndex.Add(new List()); + featureEndIndex.Add(new List()); + } + } + + for (int layerIndex = 0; layerIndex < gCodeFileToDraw.NumChangesInZ; layerIndex++) + { + CreateFeaturesForLayerIfRequired(layerIndex); + } + + if (lastRenderType != renderInfo.CurrentRenderType) + { + Clear3DGCode(); + lastRenderType = renderInfo.CurrentRenderType; + } + + if (renderFeatures.Count > 0) + { + if (Is32Bit && !GL.GlHasBufferObjects) + { + int maxFeaturesForThisSystem = 125000; + int totalFeaturesToRunder = 0; + bool cleanUnusedLayers = false; + // if on 32 bit system make sure we don't run out of memory rendering too many features + for (int i = renderInfo.EndLayerIndex - 1; i >= renderInfo.StartLayerIndex; i--) + { + if (totalFeaturesToRunder + renderFeatures[i].Count < maxFeaturesForThisSystem) + { + totalFeaturesToRunder += renderFeatures[i].Count; + } + else // don't render any of the layers below this and in fact remove them from memory if possible + { + renderInfo.startLayerIndex = i + 1; + cleanUnusedLayers = true; + break; + } + } + + if (cleanUnusedLayers) + { + // no remove any layers that are set that we are not going to render + for (int removeIndex = 0; removeIndex < layerVertexBuffer.Count; removeIndex++) + { + if (removeIndex < renderInfo.StartLayerIndex || removeIndex >= renderInfo.EndLayerIndex) + { + if (layerVertexBuffer[removeIndex] != null) + { + layerVertexBuffer[removeIndex].Dispose(); + layerVertexBuffer[removeIndex] = null; + } + } + } + } + } + + for (int i = renderInfo.EndLayerIndex - 1; i >= renderInfo.StartLayerIndex; i--) + { + // If its the first render or we change what we are trying to render then create vertex data. + if (layerVertexBuffer[i] == null) + { + VectorPOD colorVertexData = new VectorPOD(); + VectorPOD vertexIndexArray = new VectorPOD(); + + Create3DDataForLayer(i, colorVertexData, vertexIndexArray, renderInfo); + + layerVertexBuffer[i] = new GCodeVertexBuffer(); + layerVertexBuffer[i].SetVertexData(colorVertexData.Array); + layerVertexBuffer[i].SetIndexData(vertexIndexArray.Array); + } + } + + GL.Disable(EnableCap.Texture2D); + GL.PushAttrib(AttribMask.EnableBit); + GL.DisableClientState(ArrayCap.TextureCoordArray); + GL.Enable(EnableCap.PolygonSmooth); + + if (renderInfo.EndLayerIndex - 1 > renderInfo.StartLayerIndex) + { + for (int i = renderInfo.StartLayerIndex; i < renderInfo.EndLayerIndex - 1; i++) + { + int featuresOnLayer = renderFeatures[i].Count; + if (featuresOnLayer > 1) + { + layerVertexBuffer[i].renderRange(0, featureEndIndex[i][featuresOnLayer - 1]); + } + } + } + + // draw the partial layer of end-1 from startRatio to endRatio + { + int layerIndex = renderInfo.EndLayerIndex - 1; + int featuresOnLayer = renderFeatures[layerIndex].Count; + int startFeature = (int)(featuresOnLayer * renderInfo.FeatureToStartOnRatio0To1 + .5); + startFeature = Math.Max(0, Math.Min(startFeature, featuresOnLayer)); + + int endFeature = (int)(featuresOnLayer * renderInfo.FeatureToEndOnRatio0To1 + .5); + endFeature = Math.Max(0, Math.Min(endFeature, featuresOnLayer)); + + // try to make sure we always draw at least one feature + if (endFeature <= startFeature) + { + endFeature = Math.Min(startFeature + 1, featuresOnLayer); + } + if (startFeature >= endFeature) + { + // This can only happen if the start and end are set to the last feature + // Try to set the start feature to one from the end + startFeature = Math.Max(endFeature - 1, 0); + } + + if (endFeature > startFeature) + { + int ellementCount = featureEndIndex[layerIndex][endFeature - 1] - featureStartIndex[layerIndex][startFeature]; + + layerVertexBuffer[layerIndex].renderRange(featureStartIndex[layerIndex][startFeature], ellementCount); + } + } + GL.PopAttrib(); + } + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/GCodeVertexBuffer.cs b/MatterControl.OpenGL/GCodeRenderer/GCodeVertexBuffer.cs new file mode 100644 index 000000000..eb7692435 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/GCodeVertexBuffer.cs @@ -0,0 +1,136 @@ +using MatterHackers.Agg.UI; +using MatterHackers.RenderOpenGl.OpenGl; + +/* +Copyright (c) 2014, Lars Brubaker +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 System; + +namespace MatterHackers.GCodeVisualizer +{ + public class GCodeVertexBuffer : IDisposable + { + public int myIndexId; + public int myIndexLength; + public BeginMode myMode = BeginMode.Triangles; + public int myVertexId; + public int myVertexLength; + public GCodeVertexBuffer() + { + GL.GenBuffers(1, out myVertexId); + GL.GenBuffers(1, out myIndexId); + } + + public void Dispose() + { + if (myVertexId != -1) + { + int holdVertexId = myVertexId; + int holdIndexId = myIndexId; + UiThread.RunOnIdle(() => + { + GL.DeleteBuffers(1, ref holdVertexId); + GL.DeleteBuffers(1, ref holdIndexId); + }); + + myVertexId = -1; + } + } + + ~GCodeVertexBuffer() + { + Dispose(); + } + + public void renderRange(int offset, int count) + { + GL.EnableClientState(ArrayCap.ColorArray); + GL.EnableClientState(ArrayCap.NormalArray); + GL.EnableClientState(ArrayCap.VertexArray); + GL.DisableClientState(ArrayCap.TextureCoordArray); + GL.Disable(EnableCap.Texture2D); + + GL.EnableClientState(ArrayCap.IndexArray); + + GL.BindBuffer(BufferTarget.ArrayBuffer, myVertexId); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, myIndexId); + + GL.ColorPointer(4, ColorPointerType.UnsignedByte, ColorVertexData.Stride, new IntPtr(0)); + GL.NormalPointer(NormalPointerType.Float, ColorVertexData.Stride, new IntPtr(4)); + GL.VertexPointer(3, VertexPointerType.Float, ColorVertexData.Stride, new IntPtr(4 + 3 * 4)); + + GL.DrawRangeElements(myMode, 0, myIndexLength, count, DrawElementsType.UnsignedInt, new IntPtr(offset * 4)); + + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + + GL.DisableClientState(ArrayCap.IndexArray); + + GL.DisableClientState(ArrayCap.VertexArray); + GL.DisableClientState(ArrayCap.NormalArray); + GL.DisableClientState(ArrayCap.ColorArray); + } + + public void SetIndexData(int[] data) + { + SetIndexData(data, data.Length); + } + + public void SetIndexData(int[] data, int count) + { + myIndexLength = count; + GL.BindBuffer(BufferTarget.ElementArrayBuffer, myIndexId); + unsafe + { + fixed (int* dataPointer = data) + { + GL.BufferData(BufferTarget.ElementArrayBuffer, data.Length * sizeof(int), (IntPtr)dataPointer, BufferUsageHint.StaticDraw); + } + } + } + + public void SetVertexData(ColorVertexData[] data) + { + SetVertexData(data, data.Length); + } + + public void SetVertexData(ColorVertexData[] data, int count) + { + myVertexLength = count; + GL.BindBuffer(BufferTarget.ArrayBuffer, myVertexId); + unsafe + { + fixed (ColorVertexData* dataPointer = data) + { + GL.BufferData(BufferTarget.ArrayBuffer, data.Length * ColorVertexData.Stride, (IntPtr)dataPointer, BufferUsageHint.StaticDraw); + } + } + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs new file mode 100644 index 000000000..fa173e9b4 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureBase.cs @@ -0,0 +1,183 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.Agg; +using MatterHackers.VectorMath; + +namespace MatterHackers.GCodeVisualizer +{ + public abstract class RenderFeatureBase + { + protected int extruderIndex; + + public abstract void Render(Graphics2D graphics2D, GCodeRenderInfo renderInfo); + + public abstract void CreateRender3DData(VectorPOD colorVertexData, VectorPOD indexData, GCodeRenderInfo renderInfo); + + public RenderFeatureBase(int extruderIndex) + { + this.extruderIndex = extruderIndex; + } + + static public void CreateCylinder(VectorPOD colorVertexData, VectorPOD indexData, Vector3 startPos, Vector3 endPos, double radius, int steps, RGBA_Bytes color, double layerHeight) + { + Vector3 direction = endPos - startPos; + Vector3 directionNormal = direction.GetNormal(); + Vector3 startSweepDirection = Vector3.GetPerpendicular(startPos, endPos).GetNormal(); + + int[] tubeStartIndices = new int[steps]; + int[] tubeEndIndices = new int[steps]; + + int[] capStartIndices = new int[steps]; + int[] capEndIndices = new int[steps]; + + double halfHeight = layerHeight / 2 + (layerHeight * .1); + double halfWidth = radius; + double zScale = halfHeight / radius; + double xScale = halfWidth / radius; + + Vector3 scale = new Vector3(xScale, xScale, zScale); + + for (int i = 0; i < steps; i++) + { + // create tube ends verts + Vector3 tubeNormal = Vector3.Transform(startSweepDirection, Matrix4X4.CreateRotation(direction, MathHelper.Tau / (steps * 2) + MathHelper.Tau / (steps) * i)); + Vector3 offset = Vector3.Transform(startSweepDirection * radius, Matrix4X4.CreateRotation(direction, MathHelper.Tau / (steps * 2) + MathHelper.Tau / (steps) * i)); + offset *= scale; + + Vector3 tubeStart = startPos + offset; + tubeStartIndices[i] = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(tubeStart, tubeNormal, color)); + + Vector3 tubeEnd = endPos + offset; + tubeEndIndices[i] = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(tubeEnd, tubeNormal, color)); + + // create cap verts + Vector3 rotateAngle = Vector3.Cross(direction, startSweepDirection); + Vector3 capStartNormal = Vector3.Transform(startSweepDirection, Matrix4X4.CreateRotation(rotateAngle, MathHelper.Tau / 8)); + capStartNormal = Vector3.Transform(capStartNormal, Matrix4X4.CreateRotation(direction, MathHelper.Tau / (steps * 2) + MathHelper.Tau / (steps) * i)); + capStartNormal = (capStartNormal * scale).GetNormal(); + Vector3 capStartOffset = capStartNormal * radius; + capStartOffset *= scale; + Vector3 capStart = startPos + capStartOffset; + capStartIndices[i] = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(capStart, capStartNormal, color)); + + Vector3 capEndNormal = Vector3.Transform(startSweepDirection, Matrix4X4.CreateRotation(-rotateAngle, MathHelper.Tau / 8)); + capEndNormal = Vector3.Transform(capEndNormal, Matrix4X4.CreateRotation(direction, MathHelper.Tau / (steps * 2) + MathHelper.Tau / (steps) * i)); + capEndNormal = (capEndNormal * scale).GetNormal(); + Vector3 capEndOffset = capEndNormal * radius; + capEndOffset *= scale; + Vector3 capEnd = endPos + capEndOffset; + capEndIndices[i] = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(capEnd, capEndNormal, color)); + } + + int tipStartIndex = colorVertexData.Count; + Vector3 tipOffset = directionNormal * radius; + tipOffset *= scale; + colorVertexData.Add(new ColorVertexData(startPos - tipOffset, -directionNormal, color)); + int tipEndIndex = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(endPos + tipOffset, directionNormal, color)); + + for (int i = 0; i < steps; i++) + { + // create tube polys + indexData.Add(tubeStartIndices[i]); + indexData.Add(tubeEndIndices[i]); + indexData.Add(tubeEndIndices[(i + 1) % steps]); + + indexData.Add(tubeStartIndices[i]); + indexData.Add(tubeEndIndices[(i + 1) % steps]); + indexData.Add(tubeStartIndices[(i + 1) % steps]); + + // create start cap polys + indexData.Add(tubeStartIndices[i]); + indexData.Add(capStartIndices[i]); + indexData.Add(capStartIndices[(i + 1) % steps]); + + indexData.Add(tubeStartIndices[i]); + indexData.Add(capStartIndices[(i + 1) % steps]); + indexData.Add(tubeStartIndices[(i + 1) % steps]); + + // create end cap polys + indexData.Add(tubeEndIndices[i]); + indexData.Add(capEndIndices[i]); + indexData.Add(capEndIndices[(i + 1) % steps]); + + indexData.Add(tubeEndIndices[i]); + indexData.Add(capEndIndices[(i + 1) % steps]); + indexData.Add(tubeEndIndices[(i + 1) % steps]); + + // create start tip polys + indexData.Add(tipStartIndex); + indexData.Add(capStartIndices[i]); + indexData.Add(capStartIndices[(i + 1) % steps]); + + // create end tip polys + indexData.Add(tipEndIndex); + indexData.Add(capEndIndices[i]); + indexData.Add(capEndIndices[(i + 1) % steps]); + } + } + + static public void CreatePointer(VectorPOD colorVertexData, VectorPOD indexData, Vector3 startPos, Vector3 endPos, double radius, int steps, RGBA_Bytes color) + { + Vector3 direction = endPos - startPos; + Vector3 directionNormal = direction.GetNormal(); + Vector3 startSweepDirection = Vector3.GetPerpendicular(startPos, endPos).GetNormal(); + + int[] tubeStartIndices = new int[steps]; + + for (int i = 0; i < steps; i++) + { + // create tube ends verts + Vector3 tubeNormal = Vector3.Transform(startSweepDirection, Matrix4X4.CreateRotation(direction, MathHelper.Tau / (steps * 2) + MathHelper.Tau / (steps) * i)); + Vector3 offset = Vector3.Transform(startSweepDirection * radius, Matrix4X4.CreateRotation(direction, MathHelper.Tau / (steps * 2) + MathHelper.Tau / (steps) * i)); + Vector3 tubeStart = startPos + offset; + tubeStartIndices[i] = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(tubeStart, tubeNormal, color)); + Vector3 tubeEnd = endPos + offset; + } + + int tipEndIndex = colorVertexData.Count; + colorVertexData.Add(new ColorVertexData(endPos, directionNormal, color)); + + for (int i = 0; i < steps; i++) + { + // create tube polys + indexData.Add(tubeStartIndices[i]); + indexData.Add(tubeStartIndices[(i + 1) % steps]); + + indexData.Add(tipEndIndex); + } + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs new file mode 100644 index 000000000..208a10977 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureExtrusion.cs @@ -0,0 +1,157 @@ +using MatterHackers.Agg; +using MatterHackers.Agg.VertexSource; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; +/* +Copyright (c) 2014, Lars Brubaker +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 System; + +namespace MatterHackers.GCodeVisualizer +{ + public class RenderFeatureExtrusion : RenderFeatureTravel + { + private float extrusionVolumeMm3; + private float layerHeight; + private RGBA_Bytes color; + + public RenderFeatureExtrusion(Vector3 start, Vector3 end, int extruderIndex, double travelSpeed, double totalExtrusionMm, double filamentDiameterMm, double layerHeight, RGBA_Bytes color) + : base(start, end, extruderIndex, travelSpeed) + { + this.color = color; + double filamentRadius = filamentDiameterMm / 2; + double areaSquareMm = (filamentRadius * filamentRadius) * Math.PI; + + this.extrusionVolumeMm3 = (float)(areaSquareMm * totalExtrusionMm); + this.layerHeight = (float)layerHeight; + } + + private double GetRadius(RenderType renderType) + { + return GetExtrusionWidth(renderType) / 2; + } + + private double GetExtrusionWidth(RenderType renderType) + { + double width = GCodeRenderer.ExtruderWidth; + if ((renderType & RenderType.SimulateExtrusion) == RenderType.SimulateExtrusion) + { + double moveLength = (end - start).Length; + + if (moveLength > .1) // we get truncation errors from the slice engine when the length is very small, so don't do them + { + double area = extrusionVolumeMm3 / moveLength; + width = area / layerHeight; + } + } + + return width; + } + + public override void CreateRender3DData(VectorPOD colorVertexData, VectorPOD indexData, GCodeRenderInfo renderInfo) + { + if ((renderInfo.CurrentRenderType & RenderType.Extrusions) == RenderType.Extrusions) + { + Vector3Float start = this.GetStart(renderInfo); + Vector3Float end = this.GetEnd(renderInfo); + double radius = GetRadius(renderInfo.CurrentRenderType); + if ((renderInfo.CurrentRenderType & RenderType.SpeedColors) == RenderType.SpeedColors) + { + CreateCylinder(colorVertexData, indexData, new Vector3(start), new Vector3(end), radius, 6, color, layerHeight); + } + else + { + if (extruderIndex == 0) + { + CreateCylinder(colorVertexData, indexData, new Vector3(start), new Vector3(end), radius, 6, GCodeRenderer.ExtrusionColor, layerHeight); + } + else + { + CreateCylinder(colorVertexData, indexData, new Vector3(start), new Vector3(end), radius, 6, renderInfo.GetMaterialColor(extruderIndex + 1), layerHeight); + } + } + } + } + + public override void Render(Graphics2D graphics2D, GCodeRenderInfo renderInfo) + { + if (renderInfo.CurrentRenderType.HasFlag(RenderType.Extrusions)) + { + double extrusionLineWidths = GetExtrusionWidth(renderInfo.CurrentRenderType) * 2 * renderInfo.LayerScale; + + RGBA_Bytes extrusionColor = RGBA_Bytes.Black; + if (extruderIndex > 0) + { + extrusionColor = renderInfo.GetMaterialColor(extruderIndex + 1); + } + if (renderInfo.CurrentRenderType.HasFlag(RenderType.SpeedColors)) + { + extrusionColor = color; + } + + if (renderInfo.CurrentRenderType.HasFlag(RenderType.TransparentExtrusion)) + { + extrusionColor = new RGBA_Bytes(extrusionColor, 200); + } + + // render the part using opengl + Graphics2DOpenGL graphics2DGl = graphics2D as Graphics2DOpenGL; + if (graphics2DGl != null) + { + Vector3Float startF = this.GetStart(renderInfo); + Vector3Float endF = this.GetEnd(renderInfo); + Vector2 start = new Vector2(startF.x, startF.y); + renderInfo.Transform.transform(ref start); + + Vector2 end = new Vector2(endF.x, endF.y); + renderInfo.Transform.transform(ref end); + + graphics2DGl.DrawAALineRounded(start, end, extrusionLineWidths/2, extrusionColor); + } + else + { + PathStorage pathStorage = new PathStorage(); + VertexSourceApplyTransform transformedPathStorage = new VertexSourceApplyTransform(pathStorage, renderInfo.Transform); + Stroke stroke = new Stroke(transformedPathStorage, extrusionLineWidths/2); + + stroke.line_cap(LineCap.Round); + stroke.line_join(LineJoin.Round); + + Vector3Float start = this.GetStart(renderInfo); + Vector3Float end = this.GetEnd(renderInfo); + + pathStorage.Add(start.x, start.y, ShapePath.FlagsAndCommand.CommandMoveTo); + pathStorage.Add(end.x, end.y, ShapePath.FlagsAndCommand.CommandLineTo); + + graphics2D.Render(stroke, 0, extrusionColor); + } + } + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs new file mode 100644 index 000000000..cc0b0d9b8 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureRetract.cs @@ -0,0 +1,138 @@ +using MatterHackers.Agg; +using MatterHackers.Agg.VertexSource; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; + +/* +Copyright (c) 2014, Lars Brubaker +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 System; + +namespace MatterHackers.GCodeVisualizer +{ + public class RenderFeatureRetract : RenderFeatureBase + { + public static double RetractionDrawRadius = 1; + + private float extrusionAmount; + private float mmPerSecond; + private Vector3Float position; + + public RenderFeatureRetract(Vector3 position, double extrusionAmount, int extruderIndex, double mmPerSecond) + : base(extruderIndex) + { + this.extrusionAmount = (float)extrusionAmount; + this.mmPerSecond = (float)mmPerSecond; + + this.position = new Vector3Float(position); + } + + private double Radius(double layerScale) + { + double radius = RetractionDrawRadius * layerScale; + double area = Math.PI * radius * radius; + area *= Math.Abs(extrusionAmount); + radius = Math.Sqrt(area / Math.PI); + return radius; + } + + public override void CreateRender3DData(VectorPOD colorVertexData, VectorPOD indexData, GCodeRenderInfo renderInfo) + { + if ((renderInfo.CurrentRenderType & RenderType.Retractions) == RenderType.Retractions) + { + Vector3 position = new Vector3(this.position); + + if (renderInfo.CurrentRenderType.HasFlag(RenderType.HideExtruderOffsets)) + { + Vector2 offset = renderInfo.GetExtruderOffset(extruderIndex); + position = position + new Vector3(offset); + } + + RGBA_Bytes color = renderInfo.GetMaterialColor(extruderIndex + 1); + if (extruderIndex == 0) + { + if (extrusionAmount > 0) + { + color = RGBA_Bytes.Blue; + } + else + { + color = RGBA_Bytes.Red; + } + } + if (extrusionAmount > 0) + { + // unretraction + CreatePointer(colorVertexData, indexData, position + new Vector3(0, 0, 1.3), position + new Vector3(0, 0, .3), Radius(1), 5, color); + } + else + { + // retraction + CreatePointer(colorVertexData, indexData, position + new Vector3(0, 0, .3), position + new Vector3(0, 0, 1.3), Radius(1), 5, color); + } + } + } + + public override void Render(Graphics2D graphics2D, GCodeRenderInfo renderInfo) + { + if ((renderInfo.CurrentRenderType & RenderType.Retractions) == RenderType.Retractions) + { + double radius = Radius(renderInfo.LayerScale); + Vector2 position = new Vector2(this.position.x, this.position.y); + + if (renderInfo.CurrentRenderType.HasFlag(RenderType.HideExtruderOffsets)) + { + Vector2 offset = renderInfo.GetExtruderOffset(extruderIndex); + position = position + offset; + } + + renderInfo.Transform.transform(ref position); + + RGBA_Bytes retractionColor = new RGBA_Bytes(RGBA_Bytes.Red, 200); + if (extrusionAmount > 0) + { + // unretraction + retractionColor = new RGBA_Bytes(RGBA_Bytes.Blue, 200); + } + + // render the part using opengl + Graphics2DOpenGL graphics2DGl = graphics2D as Graphics2DOpenGL; + if (graphics2DGl != null) + { + graphics2DGl.DrawAACircle(position, radius, retractionColor); + } + else + { + Ellipse extrusion = new Ellipse(position, radius); + graphics2D.Render(extrusion, retractionColor); + } + } + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs new file mode 100644 index 000000000..a64936234 --- /dev/null +++ b/MatterControl.OpenGL/GCodeRenderer/RenderFeatures/RenderFeatureTravel.cs @@ -0,0 +1,138 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.Agg; +using MatterHackers.Agg.VertexSource; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; + +namespace MatterHackers.GCodeVisualizer +{ + public class RenderFeatureTravel : RenderFeatureBase + { + protected Vector3Float start; + protected Vector3Float end; + protected float travelSpeed; + + protected Vector3Float GetStart(GCodeRenderInfo renderInfo) + { + if (renderInfo.CurrentRenderType.HasFlag(RenderType.HideExtruderOffsets)) + { + Vector3Float start = this.start; + Vector2 offset = renderInfo.GetExtruderOffset(extruderIndex); + start.x += (float)offset.x; + start.y += (float)offset.y; + return start; + } + + return this.start; + } + + protected Vector3Float GetEnd(GCodeRenderInfo renderInfo) + { + if (renderInfo.CurrentRenderType.HasFlag(RenderType.HideExtruderOffsets)) + { + Vector3Float end = this.end; + Vector2 offset = renderInfo.GetExtruderOffset(extruderIndex); + end.x += (float)offset.x; + end.y += (float)offset.y; + return end; + } + + return this.end; + } + + public RenderFeatureTravel(Vector3 start, Vector3 end, int extruderIndex, double travelSpeed) + : base(extruderIndex) + { + this.extruderIndex = extruderIndex; + this.start = new Vector3Float(start); + this.end = new Vector3Float(end); + this.travelSpeed = (float)travelSpeed; + } + + public override void CreateRender3DData(VectorPOD colorVertexData, VectorPOD indexData, GCodeRenderInfo renderInfo) + { + if ((renderInfo.CurrentRenderType & RenderType.Moves) == RenderType.Moves) + { + Vector3Float start = this.GetStart(renderInfo); + Vector3Float end = this.GetEnd(renderInfo); + CreateCylinder(colorVertexData, indexData, new Vector3(start), new Vector3(end), .1, 6, GCodeRenderer.TravelColor, .2); + } + } + + public override void Render(Graphics2D graphics2D, GCodeRenderInfo renderInfo) + { + if ((renderInfo.CurrentRenderType & RenderType.Moves) == RenderType.Moves) + { + double movementLineWidth = 0.35 * renderInfo.LayerScale; + RGBA_Bytes movementColor = new RGBA_Bytes(10, 190, 15); + + // render the part using opengl + Graphics2DOpenGL graphics2DGl = graphics2D as Graphics2DOpenGL; + if (graphics2DGl != null) + { + Vector3Float startF = this.GetStart(renderInfo); + Vector3Float endF = this.GetEnd(renderInfo); + Vector2 start = new Vector2(startF.x, startF.y); + renderInfo.Transform.transform(ref start); + + Vector2 end = new Vector2(endF.x, endF.y); + renderInfo.Transform.transform(ref end); + + graphics2DGl.DrawAALineRounded(start, end, movementLineWidth, movementColor); + } + else + { + PathStorage pathStorage = new PathStorage(); + VertexSourceApplyTransform transformedPathStorage = new VertexSourceApplyTransform(pathStorage, renderInfo.Transform); + Stroke stroke = new Stroke(transformedPathStorage, movementLineWidth); + + stroke.line_cap(LineCap.Round); + stroke.line_join(LineJoin.Round); + + Vector3Float start = this.GetStart(renderInfo); + Vector3Float end = this.GetEnd(renderInfo); + + pathStorage.Add(start.x, start.y, ShapePath.FlagsAndCommand.CommandMoveTo); + if (end.x != start.x || end.y != start.y) + { + pathStorage.Add(end.x, end.y, ShapePath.FlagsAndCommand.CommandLineTo); + } + else + { + pathStorage.Add(end.x + .01, end.y, ShapePath.FlagsAndCommand.CommandLineTo); + } + + graphics2D.Render(stroke, 0, movementColor); + } + } + } + } +} \ No newline at end of file diff --git a/MatterControl.OpenGL/MatterControl.OpenGL.csproj b/MatterControl.OpenGL/MatterControl.OpenGL.csproj new file mode 100644 index 000000000..a63356258 --- /dev/null +++ b/MatterControl.OpenGL/MatterControl.OpenGL.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {CBDEEC31-D688-417B-9BF2-F0DB2E4FB268} + Library + Properties + MatterControl.OpenGL + MatterControl.OpenGL + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + + + + + + + + + + + + {97d5ade3-c1b4-4b46-8a3e-718a4f7f079f} + MatterControl.Printing + + + {657dbc6d-c3ea-4398-a3fa-ddb73c14f71b} + Agg + + + {74f6bb6c-9d02-4512-a59a-21940e35c532} + Gui + + + {545b6912-77ff-4b34-ba76-6c3d6a32be6a} + RenderOpenGl + + + {d3e41b4e-bfbb-44ca-94c8-95c00f754fdd} + VectorMath + + + + \ No newline at end of file diff --git a/MatterControl.OpenGL/Properties/AssemblyInfo.cs b/MatterControl.OpenGL/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b8b769aee --- /dev/null +++ b/MatterControl.OpenGL/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MatterControl.OpenGL")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MatterControl.OpenGL")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cbdeec31-d688-417b-9bf2-f0db2e4fb268")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MatterControl.Printing/GCode/GCodeFile.cs b/MatterControl.Printing/GCode/GCodeFile.cs new file mode 100644 index 000000000..c3adbf72b --- /dev/null +++ b/MatterControl.Printing/GCode/GCodeFile.cs @@ -0,0 +1,254 @@ +/* +Copyright (c) 2016, Lars Brubaker +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 MatterHackers.Agg; +using MatterHackers.VectorMath; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace MatterHackers.GCodeVisualizer +{ + public abstract class GCodeFile + { + private static readonly Vector4 MaxAccelerationMmPerS2 = new Vector4(1000, 1000, 100, 5000); + private static readonly Vector4 MaxVelocityMmPerS = new Vector4(500, 500, 5, 25); + private static readonly Vector4 VelocitySameAsStopMmPerS = new Vector4(8, 8, .4, 5); + +#if __ANDROID__ + protected const int Max32BitFileSize = 10000000; // 10 megs +#else + protected const int Max32BitFileSize = 100000000; // 100 megs +#endif + + public static void AssertDebugNotDefined() + { +#if DEBUG + throw new Exception("DEBUG is defined and should not be!"); +#endif + } + + #region Abstract Functions + // the number of lines in the file + public abstract int LineCount { get; } + + public abstract int NumChangesInZ { get; } + public abstract double TotalSecondsInPrint { get; } + + public abstract void Clear(); + + public abstract RectangleDouble GetBounds(); + + public abstract double GetFilamentCubicMm(double filamentDiameter); + + public abstract double GetFilamentDiameter(); + + public abstract double GetFilamentUsedMm(double filamentDiameter); + + public abstract double GetFilamentWeightGrams(double filamentDiameterMm, double density); + + public abstract double GetFirstLayerHeight(); + + public abstract int GetInstructionIndexAtLayer(int layerIndex); + + public abstract double GetLayerHeight(); + + public abstract int GetLayerIndex(int instructionIndex); + + public abstract Vector2 GetWeightedCenter(); + + public abstract PrinterMachineInstruction Instruction(int i); + + public abstract bool IsExtruding(int instructionIndexToCheck); + public abstract double PercentComplete(int instructionIndex); + + public abstract double Ratio0to1IntoContainedLayer(int instructionIndex); + #endregion Abstract Functions + + #region Static Functions + + public static int CalculateChecksum(string commandToGetChecksumFor) + { + int checksum = 0; + if (commandToGetChecksumFor.Length > 0) + { + checksum = commandToGetChecksumFor[0]; + for (int i = 1; i < commandToGetChecksumFor.Length; i++) + { + checksum ^= commandToGetChecksumFor[i]; + } + } + return checksum; + } + + public static bool IsLayerChange(string lineString) + { + return lineString.StartsWith("; LAYER:") + || lineString.StartsWith(";LAYER:"); + } + + public static bool FileTooBigToLoad(string fileName) + { + if (File.Exists(fileName) + && Is32Bit) + { + FileInfo info = new FileInfo(fileName); + // Let's make sure we can load a file this big + if (info.Length > Max32BitFileSize) + { + // It is too big to load + return true; + } + } + + return false; + } + + public static bool GetFirstNumberAfter(string stringToCheckAfter, string stringWithNumber, ref int readValue, int startIndex = 0, string stopCheckingString = ";") + { + double doubleValue = readValue; + if(GetFirstNumberAfter(stringToCheckAfter, stringWithNumber, ref doubleValue, startIndex, stopCheckingString)) + { + readValue = (int)doubleValue; + return true; + } + + return false; + } + + public static bool GetFirstNumberAfter(string stringToCheckAfter, string stringWithNumber, ref double readValue, int startIndex = 0, string stopCheckingString = ";") + { + int stringPos = stringWithNumber.IndexOf(stringToCheckAfter, startIndex); + int stopPos = stringWithNumber.IndexOf(stopCheckingString); + if (stringPos != -1 + && (stopPos == -1 || stringPos < stopPos)) + { + stringPos += stringToCheckAfter.Length; + readValue = agg_basics.ParseDouble(stringWithNumber, ref stringPos, true); + + return true; + } + + return false; + } + + public static bool GetFirstStringAfter(string stringToCheckAfter, string fullStringToLookIn, string separatorString, ref string nextString, int startIndex = 0) + { + int stringPos = fullStringToLookIn.IndexOf(stringToCheckAfter, startIndex); + if (stringPos != -1) + { + int separatorPos = fullStringToLookIn.IndexOf(separatorString, stringPos); + if (separatorPos != -1) + { + nextString = fullStringToLookIn.Substring(stringPos + stringToCheckAfter.Length, separatorPos - (stringPos + stringToCheckAfter.Length)); + return true; + } + } + + return false; + } + + public static GCodeFile Load(string fileName) + { + if (FileTooBigToLoad(fileName)) + { + return new GCodeFileStreamed(fileName); + } + else + { + return new GCodeFileLoaded(fileName); + } + } + + public static string ReplaceNumberAfter(char charToReplaceAfter, string stringWithNumber, double numberToPutIn) + { + int charPos = stringWithNumber.IndexOf(charToReplaceAfter); + if (charPos != -1) + { + int spacePos = stringWithNumber.IndexOf(" ", charPos); + if (spacePos == -1) + { + string newString = string.Format("{0}{1:0.#####}", stringWithNumber.Substring(0, charPos + 1), numberToPutIn); + return newString; + } + else + { + string newString = string.Format("{0}{1:0.#####}{2}", stringWithNumber.Substring(0, charPos + 1), numberToPutIn, stringWithNumber.Substring(spacePos)); + return newString; + } + } + + return stringWithNumber; + } + + protected static double GetSecondsThisLine(Vector3 deltaPositionThisLine, double deltaEPositionThisLine, double feedRateMmPerMin) + { + double startingVelocityMmPerS = VelocitySameAsStopMmPerS.x; + double endingVelocityMmPerS = VelocitySameAsStopMmPerS.x; + double maxVelocityMmPerS = Math.Min(feedRateMmPerMin / 60, MaxVelocityMmPerS.x); + double acceleration = MaxAccelerationMmPerS2.x; + double lengthOfThisMoveMm = Math.Max(deltaPositionThisLine.Length, deltaEPositionThisLine); + + double distanceToMaxVelocity = GetDistanceToReachEndingVelocity(startingVelocityMmPerS, maxVelocityMmPerS, acceleration); + if (distanceToMaxVelocity <= lengthOfThisMoveMm / 2) + { + // we will reach max velocity then run at it and then decelerate + double accelerationTime = GetTimeToAccelerateDistance(startingVelocityMmPerS, distanceToMaxVelocity, acceleration) * 2; + double runningTime = (lengthOfThisMoveMm - (distanceToMaxVelocity * 2)) / maxVelocityMmPerS; + return accelerationTime + runningTime; + } + else + { + // we will accelerate to the center then decelerate + double accelerationTime = GetTimeToAccelerateDistance(startingVelocityMmPerS, lengthOfThisMoveMm / 2, acceleration) * 2; + return accelerationTime; + } + } + + private static double GetDistanceToReachEndingVelocity(double startingVelocityMmPerS, double endingVelocityMmPerS, double accelerationMmPerS2) + { + double endingVelocityMmPerS2 = endingVelocityMmPerS * endingVelocityMmPerS; + double startingVelocityMmPerS2 = startingVelocityMmPerS * startingVelocityMmPerS; + return (endingVelocityMmPerS2 - startingVelocityMmPerS2) / (2.0 * accelerationMmPerS2); + } + + private static double GetTimeToAccelerateDistance(double startingVelocityMmPerS, double distanceMm, double accelerationMmPerS2) + { + // d = vi * t + .5 * a * t^2; + // t = (√(vi^2+2ad)-vi)/a + double startingVelocityMmPerS2 = startingVelocityMmPerS * startingVelocityMmPerS; + double distanceAcceleration2 = 2 * accelerationMmPerS2 * distanceMm; + return (Math.Sqrt(startingVelocityMmPerS2 + distanceAcceleration2) - startingVelocityMmPerS) / accelerationMmPerS2; + } + + private static readonly bool Is32Bit = IntPtr.Size == 4; + #endregion Static Functions + } +} \ No newline at end of file diff --git a/MatterControl.Printing/GCode/GCodeFileLoaded.cs b/MatterControl.Printing/GCode/GCodeFileLoaded.cs new file mode 100644 index 000000000..bd6840bc8 --- /dev/null +++ b/MatterControl.Printing/GCode/GCodeFileLoaded.cs @@ -0,0 +1,969 @@ +/* +Copyright (c) 2014, Lars Brubaker +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. +*/ +#define MULTI_THREAD +#define DUMP_SLOW_TIMES + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.VectorMath; + +namespace MatterHackers.GCodeVisualizer +{ + public class GCodeFileLoaded : GCodeFile + { + private double amountOfAccumulatedEWhileParsing = 0; + + private List indexOfChangeInZ = new List(); + private Vector2 center = Vector2.Zero; + private double parsingLastZ; + private bool gcodeHasExplicitLayerChangeInfo = false; + private double firstLayerThickness; + private double layerThickness; + + private double filamentUsedMmCache = 0; + private double diameterOfFilamentUsedMmCache = 0; + + private List GCodeCommandQueue = new List(); + + public GCodeFileLoaded(bool gcodeHasExplicitLayerChangeInfo = false) + { + this.gcodeHasExplicitLayerChangeInfo = gcodeHasExplicitLayerChangeInfo; + } + + public GCodeFileLoaded(string pathAndFileName, bool gcodeHasExplicitLayerChangeInfo = false) + { + this.gcodeHasExplicitLayerChangeInfo = gcodeHasExplicitLayerChangeInfo; + this.Load(pathAndFileName); + } + + public override PrinterMachineInstruction Instruction(int index) + { + return GCodeCommandQueue[index]; + } + + public override int LineCount => GCodeCommandQueue.Count; + + public override void Clear() + { + indexOfChangeInZ.Clear(); + GCodeCommandQueue.Clear(); + } + + 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 < indexOfChangeInZ.Count; i++) + { + if (insertIndex < indexOfChangeInZ[i]) + { + indexOfChangeInZ[i]++; + } + } + + GCodeCommandQueue.Insert(insertIndex, printerMachineInstruction); + } + + public static GCodeFile ParseGCodeString(string gcodeContents) + { + return ParseFileContents(gcodeContents, null); + } + + public static GCodeFileLoaded Load(Stream fileStream) + { + GCodeFileLoaded loadedGCode = null; + try + { + string gCodeString = ""; + using (var reader = new StreamReader(fileStream)) + { + gCodeString = reader.ReadToEnd(); + } + + loadedGCode = ParseFileContents(gCodeString, null); + } + catch (IOException e) + { + Debug.Print(e.Message); + } + + return loadedGCode; + } + + public static async Task LoadInBackground(string fileName, ReportProgressRatio progressReporter) + { + if (Path.GetExtension(fileName).ToUpper() == ".GCODE") + { + try + { + if (File.Exists(fileName) && !FileTooBigToLoad(fileName)) + { + return await Task.Run(() => + { + return ParseFileContents(File.ReadAllText(fileName), progressReporter); + }); + } + } + catch (IOException e) + { + Debug.Print(e.Message); + } + } + + return null; + } + + public new void Load(string gcodePathAndFileName) + { + if (!FileTooBigToLoad(gcodePathAndFileName)) + { + using (FileStream fileStream = new FileStream(gcodePathAndFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (StreamReader streamReader = new StreamReader(fileStream)) + { + GCodeFileLoaded loadedFile = GCodeFileLoaded.Load(streamReader.BaseStream); + + this.indexOfChangeInZ = loadedFile.indexOfChangeInZ; + this.center = loadedFile.center; + this.parsingLastZ = loadedFile.parsingLastZ; + + this.GCodeCommandQueue = loadedFile.GCodeCommandQueue; + } + } + } + } + + 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 int CountNumLines(string gCodeString) + { + int crCount = 0; + foreach (char testCharacter in gCodeString) + { + if (testCharacter == '\n') + { + crCount++; + } + } + + return crCount + 1; + } + + public static GCodeFileLoaded ParseFileContents(string gCodeString, ReportProgressRatio progressReporter) + { + if (gCodeString == null) + { + return null; + } + + Stopwatch loadTime = Stopwatch.StartNew(); + + Stopwatch maxProgressReport = new Stopwatch(); + maxProgressReport.Start(); + PrinterMachineInstruction machineInstructionForLine = new PrinterMachineInstruction("None"); + + bool gcodeHasExplicitLayerChangeInfo = false; + if (gCodeString.Contains("LAYER:")) + { + gcodeHasExplicitLayerChangeInfo = true; + } + + GCodeFileLoaded loadedGCodeFile = new GCodeFileLoaded(gcodeHasExplicitLayerChangeInfo); + + int crCount = CountNumLines(gCodeString); + int lineIndex = 0; + foreach (string outputString in CustomSplit(gCodeString, '\n')) + { + string lineString = outputString.Trim(); + machineInstructionForLine = new PrinterMachineInstruction(lineString, machineInstructionForLine, false); + + if (lineString.Length > 0) + { + switch (lineString[0]) + { + case 'G': + loadedGCodeFile.ParseGLine(lineString, machineInstructionForLine); + break; + + case 'M': + loadedGCodeFile.ParseMLine(lineString, machineInstructionForLine); + break; + + case 'T': + double extruderIndex = 0; + if (GetFirstNumberAfter("T", lineString, ref extruderIndex)) + { + machineInstructionForLine.ExtruderIndex = (int)extruderIndex; + } + break; + + case ';': + if (gcodeHasExplicitLayerChangeInfo && IsLayerChange(lineString)) + { + loadedGCodeFile.IndexOfChangeInZ.Add(loadedGCodeFile.GCodeCommandQueue.Count); + } + if (lineString.StartsWith("; layerThickness")) + { + loadedGCodeFile.layerThickness = double.Parse(lineString.Split('=')[1]); + } + else if (lineString.StartsWith("; firstLayerThickness") && loadedGCodeFile.firstLayerThickness == 0) + { + loadedGCodeFile.firstLayerThickness = double.Parse(lineString.Split('=')[1]); + } + break; + + case '@': + break; + + default: +#if DEBUG + throw new NotImplementedException(); +#else + break; +#endif + } + } + + loadedGCodeFile.GCodeCommandQueue.Add(machineInstructionForLine); + + if (progressReporter != null && maxProgressReport.ElapsedMilliseconds > 200) + { + progressReporter((double)lineIndex / crCount / 2, "", out bool continueProcessing); + if (!continueProcessing) + { + return null; + } + + maxProgressReport.Restart(); + } + + lineIndex++; + } + + loadedGCodeFile.AnalyzeGCodeLines(progressReporter); + + loadTime.Stop(); + Console.WriteLine("Time To Load Seconds: {0:0.00}".FormatWith(loadTime.Elapsed.TotalSeconds)); + + return loadedGCodeFile; + } + + private void AnalyzeGCodeLines(ReportProgressRatio progressReporter) + { + double feedRateMmPerMin = 0; + Vector3 lastPrinterPosition = new Vector3(); + double lastEPosition = 0; + + Stopwatch maxProgressReport = new Stopwatch(); + maxProgressReport.Start(); + + for (int lineIndex = 0; lineIndex < GCodeCommandQueue.Count; lineIndex++) + { + PrinterMachineInstruction instruction = GCodeCommandQueue[lineIndex]; + string line = instruction.Line; + Vector3 deltaPositionThisLine = new Vector3(); + double deltaEPositionThisLine = 0; + string lineToParse = line.ToUpper().Trim(); + if (lineToParse.StartsWith("G0") || lineToParse.StartsWith("G1")) + { + double newFeedRateMmPerMin = 0; + if (GetFirstNumberAfter("F", lineToParse, ref newFeedRateMmPerMin)) + { + feedRateMmPerMin = newFeedRateMmPerMin; + } + + Vector3 attemptedDestination = lastPrinterPosition; + GetFirstNumberAfter("X", lineToParse, ref attemptedDestination.x); + GetFirstNumberAfter("Y", lineToParse, ref attemptedDestination.y); + GetFirstNumberAfter("Z", lineToParse, ref attemptedDestination.z); + + double ePosition = lastEPosition; + GetFirstNumberAfter("E", lineToParse, ref ePosition); + + deltaPositionThisLine = attemptedDestination - lastPrinterPosition; + deltaEPositionThisLine = Math.Abs(ePosition - lastEPosition); + + lastPrinterPosition = attemptedDestination; + lastEPosition = ePosition; + } + else if (lineToParse.StartsWith("G92")) + { + double ePosition = 0; + if (GetFirstNumberAfter("E", lineToParse, ref ePosition)) + { + lastEPosition = ePosition; + } + } + + if (feedRateMmPerMin > 0) + { + instruction.secondsThisLine = (float)GetSecondsThisLine(deltaPositionThisLine, deltaEPositionThisLine, feedRateMmPerMin); + } + + if (progressReporter != null && maxProgressReport.ElapsedMilliseconds > 200) + { + progressReporter(((double) lineIndex / GCodeCommandQueue.Count / 2) + .5, "", out bool continueProcessing); + if (!continueProcessing) + { + return; + } + + maxProgressReport.Restart(); + } + } + + double accumulatedTime = 0; + for (int i = GCodeCommandQueue.Count - 1; i >= 0; i--) + { + PrinterMachineInstruction line = GCodeCommandQueue[i]; + accumulatedTime += line.secondsThisLine; + line.secondsToEndFromHere = (float)accumulatedTime; + } + } + + public Vector2 Center + { + get { return center; } + } + + 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 int GetInstructionIndexAtLayer(int layerIndex) + { + return IndexOfChangeInZ[layerIndex]; + } + + private List IndexOfChangeInZ + { + get { return indexOfChangeInZ; } + } + + public override int NumChangesInZ + { + get { return indexOfChangeInZ.Count; } + } + + private void ParseMLine(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(' '); + switch (splitOnSpace[0].Substring(1).Trim()) + { + case "01": + // show a message? + break; + + case "6": + // wait for tool to heat up (wait for condition?) + break; + + case "101": + // extrude on, forward + break; + + case "18": + // turn off stepers + break; + + case "42": + // Stop on material exhausted / Switch I/O pin + break; + + case "72": + // makerbot, Play tone or song + break; + + case "73": + // makerbot, Manually set build percentage + break; + + case "82": + // set extruder to absolute mode + break; + + case "83": + //Set extruder to relative mode + break; + + case "84": + // lineString = "M84 ; disable motors\r" + break; + + case "92": + // set steps per mm + break; + + case "102": + // extrude on reverse + break; + + case "103": + // extrude off + break; + + case "104": + // set extruder tempreature + break; + + case "105": + // M105 Custom code for temperature reading. (Not used) + break; + + case "106": + // turn fan on + break; + + case "107": + // turn fan off + break; + + case "108": + // set extruder speed + break; + + case "109": + // set heated platform temperature + break; + + case "114": + break; + + case "117": + // in Marlin: Display Message + break; + + case "126": + // enable fan (makerbot) + break; + + case "127": + // disable fan (makerbot) + break; + + case "132": + // recall stored home offsets for axis xyzab + break; + + case "133": + // MakerBot wait for toolhead to heat + break; + + case "134": + // MakerBot wait for platform to reach target temp + break; + + case "135": + // MakerBot change toolhead + break; + + case "140": + // set bed temperature + break; + + case "190": + // wait for bed temperature to be reached + break; + + case "200": + // M200 sets the filament diameter. + break; + + case "201": + // set axis acceleration + break; + + case "204": // - Set default acceleration + break; + + case "207": // M207: calibrate z axis by detecting z max length + break; + + case "208": // M208: set axis max travel + break; + + case "209": // M209: enable automatic retract + break; + + case "210": // Set homing rate + break; + + case "226": // user request pause + break; + + case "227": // Enable Automatic Reverse and Prime + break; + + case "301": + break; + + case "400": // Wait for current moves to finish + break; + + 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; + + default: + 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 miliseconds + break; + + case "1": + // get the x y z to move to + { + double valueX = 0; + if (GCodeFile.GetFirstNumberAfter("X", lineString, ref valueX)) + { + processingMachineState.X = valueX; + } + double valueY = 0; + if (GCodeFile.GetFirstNumberAfter("Y", lineString, ref valueY)) + { + processingMachineState.Y = valueY; + } + double valueZ = 0; + if (GCodeFile.GetFirstNumberAfter("Z", lineString, ref valueZ)) + { + processingMachineState.Z = valueZ; + } + double valueE = 0; + if (GCodeFile.GetFirstNumberAfter("E", lineString, ref valueE)) + { + if (processingMachineState.movementType == PrinterMachineInstruction.MovementTypes.Absolute) + { + processingMachineState.EPosition = valueE + amountOfAccumulatedEWhileParsing; + } + else + { + processingMachineState.EPosition += valueE; + } + } + double valueF = 0; + if (GCodeFile.GetFirstNumberAfter("F", lineString, ref valueF)) + { + processingMachineState.FeedRate = valueF; + } + } + + if (!gcodeHasExplicitLayerChangeInfo) + { + if (processingMachineState.Z != parsingLastZ || indexOfChangeInZ.Count == 0) + { + // if we changed z or there is a movement and we have never started a layer index + indexOfChangeInZ.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 ePosition = 0; + if (GetFirstNumberAfter("E", lineString, ref ePosition)) + { + // remember how much e position we just gave up + amountOfAccumulatedEWhileParsing = (processingMachineState.EPosition - ePosition); + } + 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() + { + Vector2 total = new Vector2(); +#if !MULTI_THREAD + foreach (PrinterMachineInstruction state in GCodeCommandQueue) + { + total += new Vector2(state.Position.x, state.Position.y); + } +#else + Parallel.For( + 0, + GCodeCommandQueue.Count, + () => new Vector2(), + (int index, ParallelLoopState loop, Vector2 subtotal) => + { + PrinterMachineInstruction state = GCodeCommandQueue[index]; + subtotal += new Vector2(state.Position.x, state.Position.y); + return subtotal; + }, + (x) => + { + total += new Vector2(x.x, x.y); + } + ); +#endif + + return total / GCodeCommandQueue.Count; + } + + public override RectangleDouble GetBounds() + { + RectangleDouble bounds = new RectangleDouble(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue); +#if !MULTI_THREAD + 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); + } +#else + Parallel.For( + 0, + GCodeCommandQueue.Count, + () => new RectangleDouble(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue), + (int index, ParallelLoopState loop, RectangleDouble subtotal) => + { + PrinterMachineInstruction state = GCodeCommandQueue[index]; + subtotal.Left = Math.Min(state.Position.x, subtotal.Left); + subtotal.Right = Math.Max(state.Position.x, subtotal.Right); + subtotal.Bottom = Math.Min(state.Position.y, subtotal.Bottom); + subtotal.Top = Math.Max(state.Position.y, subtotal.Top); + + return subtotal; + }, + (x) => + { + bounds.Left = Math.Min(x.Left, bounds.Left); + bounds.Right = Math.Max(x.Right, bounds.Right); + bounds.Bottom = Math.Min(x.Bottom, bounds.Bottom); + bounds.Top = Math.Max(x.Top, bounds.Top); + } + ); +#endif + return bounds; + } + + public override bool IsExtruding(int instructionIndexToCheck) + { + if (instructionIndexToCheck > 1 && instructionIndexToCheck < GCodeCommandQueue.Count) + { + double extrusionLengeth = GCodeCommandQueue[instructionIndexToCheck].EPosition - GCodeCommandQueue[instructionIndexToCheck - 1].EPosition; + if (extrusionLengeth > 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) + { + for (int i = 0; i < Math.Min(20, GCodeCommandQueue.Count); i++) + { + if (GetFirstNumberAfter("filamentDiameter =", GCodeCommandQueue[i].Line, ref filamentDiameterCache)) + { + break; + } + } + + if (filamentDiameterCache == 0) + { + // didn't find it, so look at the end of the file for filament_diameter = + string lookFor = "; filament_diameter =";// 2.85 + for (int i = GCodeCommandQueue.Count - 1; i > Math.Max(0, GCodeCommandQueue.Count - 100); i--) + { + if (GetFirstNumberAfter(lookFor, GCodeCommandQueue[i].Line, ref filamentDiameterCache)) + { + } + } + } + + if(filamentDiameterCache == 0) + { + // it is still 0 so set it to something so we render + filamentDiameterCache = 1.75; + } + } + + return filamentDiameterCache; + } + + public override double GetLayerHeight() + { + if (layerThickness > 0) + { + return layerThickness; + } + + if (indexOfChangeInZ.Count > 2) + { + return GCodeCommandQueue[IndexOfChangeInZ[2]].Z - GCodeCommandQueue[IndexOfChangeInZ[1]].Z; + } + + return .5; + } + + public override double GetFirstLayerHeight() + { + if (firstLayerThickness > 0) + { + return firstLayerThickness; + } + + if (indexOfChangeInZ.Count > 1) + { + return GCodeCommandQueue[IndexOfChangeInZ[1]].Z - GCodeCommandQueue[IndexOfChangeInZ[0]].Z; + } + + return .5; + } + + public override int GetLayerIndex(int instructionIndex) + { + if (instructionIndex >= 0 + && instructionIndex < LineCount) + { + for (int zIndex = 0; zIndex < NumChangesInZ; zIndex++) + { + if (instructionIndex < IndexOfChangeInZ[zIndex]) + { + return zIndex; + } + } + + return NumChangesInZ - 1; + } + + return -1; + } + + public override double Ratio0to1IntoContainedLayer(int instructionIndex) + { + int currentLayer = GetLayerIndex(instructionIndex); + + if (currentLayer > -1) + { + int startIndex = 0; + if (currentLayer > 0) + { + startIndex = IndexOfChangeInZ[currentLayer - 1]; + } + int endIndex = LineCount - 1; + if (currentLayer < NumChangesInZ - 1) + { + endIndex = IndexOfChangeInZ[currentLayer]; + } + + int deltaFromStart = Math.Max(0, instructionIndex - startIndex); + return deltaFromStart / (double)(endIndex - startIndex); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/MatterControl.Printing/GCode/GCodeFileStreamed.cs b/MatterControl.Printing/GCode/GCodeFileStreamed.cs new file mode 100644 index 000000000..9e6017a6b --- /dev/null +++ b/MatterControl.Printing/GCode/GCodeFileStreamed.cs @@ -0,0 +1,286 @@ +/* +Copyright (c) 2014, Lars Brubaker +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. +*/ +#define MULTI_THREAD + +using MatterHackers.Agg; +using MatterHackers.VectorMath; +using System; +using System.IO; + +namespace MatterHackers.GCodeVisualizer +{ + public class GCodeFileStreamed : GCodeFile + { + private StreamReader openGcodeStream; + object locker = new object(); + + private bool readLastLineOfFile = false; + private int readLineCount = 0; + private const int MaxLinesToBuffer = 128; + private PrinterMachineInstruction[] readLinesRingBuffer = new PrinterMachineInstruction[MaxLinesToBuffer]; + + public GCodeFileStreamed(string fileName) + { + var inStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + openGcodeStream = new StreamReader(inStream); + } + + ~GCodeFileStreamed() + { + CloseStream(); + } + + private void CloseStream() + { + if (openGcodeStream != null) + { + openGcodeStream.Close(); + openGcodeStream = null; + } + } + + public override int LineCount + { + get + { + if (openGcodeStream != null + && !readLastLineOfFile) + { + return Math.Max(readLineCount + 1, (int)(openGcodeStream.BaseStream.Length / 14)); + } + + return readLineCount; + } + } + + public long ByteCount + { + get + { + if (openGcodeStream != null + && !readLastLineOfFile) + { + return openGcodeStream.BaseStream.Length; + } + + return 0; + } + } + + public long BytePosition + { + get + { + if (openGcodeStream != null + && !readLastLineOfFile) + { + return openGcodeStream.BaseStream.Position; + } + + return 0; + } + } + + public override double TotalSecondsInPrint + { + get + { + // We don't know, so we always return -1 used to identify as streaming. + return -1; + } + } + + public override void Clear() + { + CloseStream(); + + readLastLineOfFile = false; + readLineCount = 0; + } + + public override Vector2 GetWeightedCenter() + { + throw new NotImplementedException("A streamed GCode file should not need to do this. Please validate the code that is calling this."); + } + + public override RectangleDouble GetBounds() + { + throw new NotImplementedException("A streamed GCode file should not need to do this. Please validate the code that is calling this."); + } + + public override double GetFilamentCubicMm(double filamentDiameter) + { + throw new NotImplementedException("A streamed GCode file should not need to do this. Please validate the code that is calling this."); + } + + public override bool IsExtruding(int instructionIndexToCheck) + { + throw new NotImplementedException(); + } + + public override double GetLayerHeight() + { + throw new NotImplementedException(); + } + + public override double GetFirstLayerHeight() + { + throw new NotImplementedException(); + } + + public override double GetFilamentUsedMm(double filamentDiameter) + { + throw new NotImplementedException(); + } + + public override double PercentComplete(int instructionIndex) + { + lock(locker) + { + if (openGcodeStream != null + && openGcodeStream.BaseStream.Length > 0) + { + return (double)openGcodeStream.BaseStream.Position / (double)openGcodeStream.BaseStream.Length * 100.0; + } + } + + return 100; + } + + public override int GetInstructionIndexAtLayer(int layerIndex) + { + return 0; + } + + public override double GetFilamentDiameter() + { + return 0; + } + + public override double GetFilamentWeightGrams(double filamentDiameterMm, double density) + { + return 0; + } + + public override int GetLayerIndex(int instructionIndex) + { + return 0; + } + + public override int NumChangesInZ + { + get + { + return 0; + } + } + + private double feedRateMmPerMin = 0; + private Vector3 lastPrinterPosition = new Vector3(); + private double lastEPosition = 0; + + public override PrinterMachineInstruction Instruction(int index) + { + lock(locker) + { + if (index < readLineCount - MaxLinesToBuffer) + { + throw new Exception("You are asking for a line we no longer have buffered"); + } + + while (index >= readLineCount) + { + string line = openGcodeStream.ReadLine(); + if (line == null) + { + readLastLineOfFile = true; + line = ""; + } + + int ringBufferIndex = readLineCount % MaxLinesToBuffer; + readLinesRingBuffer[ringBufferIndex] = new PrinterMachineInstruction(line); + + PrinterMachineInstruction instruction = readLinesRingBuffer[ringBufferIndex]; + Vector3 deltaPositionThisLine = new Vector3(); + double deltaEPositionThisLine = 0; + string lineToParse = line.ToUpper().Trim(); + if (lineToParse.StartsWith("G0") || lineToParse.StartsWith("G1")) + { + double newFeedRateMmPerMin = 0; + if (GetFirstNumberAfter("F", lineToParse, ref newFeedRateMmPerMin)) + { + feedRateMmPerMin = newFeedRateMmPerMin; + } + + Vector3 attemptedDestination = lastPrinterPosition; + GetFirstNumberAfter("X", lineToParse, ref attemptedDestination.x); + GetFirstNumberAfter("Y", lineToParse, ref attemptedDestination.y); + GetFirstNumberAfter("Z", lineToParse, ref attemptedDestination.z); + + double ePosition = lastEPosition; + GetFirstNumberAfter("E", lineToParse, ref ePosition); + + deltaPositionThisLine = attemptedDestination - lastPrinterPosition; + deltaEPositionThisLine = Math.Abs(ePosition - lastEPosition); + + lastPrinterPosition = attemptedDestination; + lastEPosition = ePosition; + } + else if (lineToParse.StartsWith("G92")) + { + double ePosition = 0; + if (GetFirstNumberAfter("E", lineToParse, ref ePosition)) + { + lastEPosition = ePosition; + } + } + + if (feedRateMmPerMin > 0) + { + instruction.secondsThisLine = (float)GetSecondsThisLine(deltaPositionThisLine, deltaEPositionThisLine, feedRateMmPerMin); + } + + readLineCount++; + } + } + + return readLinesRingBuffer[index % MaxLinesToBuffer]; + } + + public override double Ratio0to1IntoContainedLayer(int instructionIndex) + { + if (ByteCount != 0) + { + return BytePosition / (double)ByteCount; + } + + return 1; + } + } +} diff --git a/MatterControl.Printing/GCode/PrinterMachineInstruction.cs b/MatterControl.Printing/GCode/PrinterMachineInstruction.cs new file mode 100644 index 000000000..a9565df47 --- /dev/null +++ b/MatterControl.Printing/GCode/PrinterMachineInstruction.cs @@ -0,0 +1,152 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.VectorMath; +using System.Text; + +namespace MatterHackers.GCodeVisualizer +{ + public class PrinterMachineInstruction + { + public byte[] byteLine; + + public string Line + { + get + { + return Encoding.Default.GetString(byteLine); + } + + set + { + byteLine = Encoding.Default.GetBytes(value); + } + } + + private Vector3Float xyzPosition = new Vector3Float(); + private float ePosition = 0; + private float feedRate = 0; + + public enum MovementTypes { Absolute, Relative }; + + // Absolute is the RepRap default + public MovementTypes movementType = MovementTypes.Absolute; + + public float secondsThisLine; + public float secondsToEndFromHere; + public bool clientInsertion; + + public PrinterMachineInstruction(string Line) + { + this.Line = Line; + } + + public PrinterMachineInstruction(string Line, PrinterMachineInstruction copy, bool clientInsertion = false) + : this(Line) + { + xyzPosition = copy.xyzPosition; + feedRate = copy.feedRate; + ePosition = copy.ePosition; + movementType = copy.movementType; + secondsToEndFromHere = copy.secondsToEndFromHere; + ExtruderIndex = copy.ExtruderIndex; + this.clientInsertion = clientInsertion; + } + + public int ExtruderIndex { get; set; } + + public Vector3 Position + { + get { return new Vector3(xyzPosition); } + } + + public double X + { + get { return xyzPosition.x; } + set + { + if (movementType == MovementTypes.Absolute) + { + xyzPosition.x = (float)value; + } + else + { + xyzPosition.x += (float)value; + } + } + } + + public double Y + { + get { return xyzPosition.y; } + set + { + if (movementType == MovementTypes.Absolute) + { + xyzPosition.y = (float)value; + } + else + { + xyzPosition.y += (float)value; + } + } + } + + public double Z + { + get { return xyzPosition.z; } + set + { + if (movementType == MovementTypes.Absolute) + { + xyzPosition.z = (float)value; + } + else + { + xyzPosition.z += (float)value; + } + } + } + + public double EPosition + { + get { return ePosition; } + set + { + ePosition = (float)value; + } + } + + public double FeedRate + { + get { return feedRate; } + set { feedRate = (float)value; } + } + } +} \ No newline at end of file diff --git a/MatterControl.Printing/MatterControl.Printing.csproj b/MatterControl.Printing/MatterControl.Printing.csproj new file mode 100644 index 000000000..da9ab2ae4 --- /dev/null +++ b/MatterControl.Printing/MatterControl.Printing.csproj @@ -0,0 +1,61 @@ + + + + + Debug + AnyCPU + {97D5ADE3-C1B4-4B46-8A3E-718A4F7F079F} + Library + Properties + MatterControl.Printing + MatterControl.Printing + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {657dbc6d-c3ea-4398-a3fa-ddb73c14f71b} + Agg + + + {d3e41b4e-bfbb-44ca-94c8-95c00f754fdd} + VectorMath + + + + \ No newline at end of file diff --git a/MatterControl.Printing/Properties/AssemblyInfo.cs b/MatterControl.Printing/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..bdf9b5fb5 --- /dev/null +++ b/MatterControl.Printing/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MatterControl.Printing")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MatterControl.Printing")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("97d5ade3-c1b4-4b46-8a3e-718a4f7f079f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MatterControl.csproj b/MatterControl.csproj index 1d2d4dd6e..05bf214cb 100644 --- a/MatterControl.csproj +++ b/MatterControl.csproj @@ -209,6 +209,9 @@ + + + @@ -474,6 +477,14 @@ + + {CBDEEC31-D688-417B-9BF2-F0DB2E4FB268} + MatterControl.OpenGL + + + {97D5ADE3-C1B4-4B46-8A3E-718A4F7F079F} + MatterControl.Printing + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} clipper_library @@ -518,10 +529,6 @@ {D3E41B4E-BFBB-44CA-94C8-95C00F754FDD} VectorMath - - {F67AE800-B0C7-42A8-836F-597B4E74591C} - GCodeVisualizer - {D3ABF72C-64C2-4E51-A119-E077210FA990} SerialPortCommunication @@ -534,10 +541,6 @@ {C46CA728-DD2F-4DD1-971A-AAA89D9DFF95} MatterSlice - - {A737BC76-165B-46C6-82B7-8871C7C92942} - MeshViewer - {AE37DE1F-22F7-49EE-8732-FC6BC8DC58D9} Tesselate diff --git a/PartPreviewWindow/View3D/InteractionVolume.cs b/PartPreviewWindow/View3D/InteractionVolume.cs new file mode 100644 index 000000000..a94f51895 --- /dev/null +++ b/PartPreviewWindow/View3D/InteractionVolume.cs @@ -0,0 +1,206 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 System; +using MatterHackers.Agg; +using MatterHackers.Agg.Font; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.Transform; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.RayTracer; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; + +namespace MatterHackers.MeshVisualizer +{ + [Flags] + public enum LineArrows { None = 0, Start = 1, End = 2, Both = 3 }; + + public class InteractionVolume + { + public bool MouseDownOnControl; + public Matrix4X4 TotalTransform = Matrix4X4.Identity; + + private bool mouseOver = false; + + public InteractionVolume(IPrimitive collisionVolume, MeshViewerWidget meshViewerToDrawWith) + { + this.CollisionVolume = collisionVolume; + this.MeshViewerToDrawWith = meshViewerToDrawWith; + } + + public IPrimitive CollisionVolume { get; set; } + + public bool DrawOnTop { get; protected set; } + + protected MeshViewerWidget MeshViewerToDrawWith { get; } + + public IntersectInfo MouseMoveInfo { get; set; } + + public bool MouseOver + { + get + { + return mouseOver; + } + + set + { + if (mouseOver != value) + { + mouseOver = value; + Invalidate(); + } + } + } + + public static Vector3 SetBottomControlHeight(AxisAlignedBoundingBox originalSelectedBounds, Vector3 cornerPosition) + { + if (originalSelectedBounds.minXYZ.z < 0) + { + if (originalSelectedBounds.maxXYZ.z < 0) + { + cornerPosition.z = originalSelectedBounds.maxXYZ.z; + } + else + { + cornerPosition.z = 0; + } + } + + return cornerPosition; + } + + public static void DrawMeasureLine(Graphics2D graphics2D, Vector2 lineStart, Vector2 lineEnd, RGBA_Bytes color, LineArrows arrows) + { + graphics2D.Line(lineStart, lineEnd, RGBA_Bytes.Black); + + Vector2 direction = lineEnd - lineStart; + if (direction.LengthSquared > 0 + && (arrows.HasFlag(LineArrows.Start) || arrows.HasFlag(LineArrows.End))) + { + PathStorage arrow = new PathStorage(); + arrow.MoveTo(-3, -5); + arrow.LineTo(0, 0); + arrow.LineTo(3, -5); + if (arrows.HasFlag(LineArrows.End)) + { + double rotation = Math.Atan2(direction.y, direction.x); + IVertexSource correctRotation = new VertexSourceApplyTransform(arrow, Affine.NewRotation(rotation - MathHelper.Tau / 4)); + IVertexSource inPosition = new VertexSourceApplyTransform(correctRotation, Affine.NewTranslation(lineEnd)); + graphics2D.Render(inPosition, RGBA_Bytes.Black); + } + if (arrows.HasFlag(LineArrows.Start)) + { + double rotation = Math.Atan2(direction.y, direction.x) + MathHelper.Tau / 2; + IVertexSource correctRotation = new VertexSourceApplyTransform(arrow, Affine.NewRotation(rotation - MathHelper.Tau / 4)); + IVertexSource inPosition = new VertexSourceApplyTransform(correctRotation, Affine.NewTranslation(lineStart)); + graphics2D.Render(inPosition, RGBA_Bytes.Black); + } + } + } + + public virtual void Draw2DContent(Agg.Graphics2D graphics2D) + { + } + + public virtual void DrawGlContent(EventArgs e) + { + } + + public void Invalidate() + { + MeshViewerToDrawWith.Invalidate(); + } + + public virtual void OnMouseDown(MouseEvent3DArgs mouseEvent3D) + { + MouseDownOnControl = true; + MeshViewerToDrawWith.Invalidate(); + } + + public virtual void OnMouseMove(MouseEvent3DArgs mouseEvent3D) + { + } + + public virtual void OnMouseUp(MouseEvent3DArgs mouseEvent3D) + { + MouseDownOnControl = false; + } + + public virtual void SetPosition(IObject3D selectedItem) + { + } + } + + public class ValueDisplayInfo + { + private string formatString; + private string measureDisplayedString = ""; + private ImageBuffer measureDisplayImage = null; + private string unitsString; + + public ValueDisplayInfo(string formatString = "{0:0.00}", string unitsString = "mm") + { + this.formatString = formatString; + this.unitsString = unitsString; + } + + public void DisplaySizeInfo(Graphics2D graphics2D, Vector2 widthDisplayCenter, double size) + { + string displayString = formatString.FormatWith(size); + if (measureDisplayImage == null || measureDisplayedString != displayString) + { + measureDisplayedString = displayString; + TypeFacePrinter printer = new TypeFacePrinter(measureDisplayedString, 16); + TypeFacePrinter unitPrinter = new TypeFacePrinter(unitsString, 10); + Double unitPrinterOffset = 1; + + BorderDouble margin = new BorderDouble(5); + printer.Origin = new Vector2(margin.Left, margin.Bottom); + RectangleDouble bounds = printer.LocalBounds; + + unitPrinter.Origin = new Vector2(bounds.Right + unitPrinterOffset, margin.Bottom); + RectangleDouble unitPrinterBounds = unitPrinter.LocalBounds; + + measureDisplayImage = new ImageBuffer((int)(bounds.Width + margin.Width + unitPrinterBounds.Width + unitPrinterOffset), (int)(bounds.Height + margin.Height)); + // make sure the texture has mipmaps (so it can reduce well) + ImageGlPlugin glPlugin = ImageGlPlugin.GetImageGlPlugin(measureDisplayImage, true); + Graphics2D widthGraphics = measureDisplayImage.NewGraphics2D(); + widthGraphics.Clear(new RGBA_Bytes(RGBA_Bytes.White, 128)); + printer.Render(widthGraphics, RGBA_Bytes.Black); + unitPrinter.Render(widthGraphics, RGBA_Bytes.Black); + } + + widthDisplayCenter -= new Vector2(measureDisplayImage.Width / 2, measureDisplayImage.Height / 2); + graphics2D.Render(measureDisplayImage, widthDisplayCenter); + } + } +} \ No newline at end of file diff --git a/PartPreviewWindow/View3D/MeshViewerWidget.cs b/PartPreviewWindow/View3D/MeshViewerWidget.cs new file mode 100644 index 000000000..231529813 --- /dev/null +++ b/PartPreviewWindow/View3D/MeshViewerWidget.cs @@ -0,0 +1,888 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.Agg; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.OpenGlGui; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.PolygonMesh; +using MatterHackers.PolygonMesh.Processors; +using MatterHackers.RayTracer; +using MatterHackers.RayTracer.Traceable; +using MatterHackers.RenderOpenGl; +using MatterHackers.RenderOpenGl.OpenGl; +using MatterHackers.VectorMath; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace MatterHackers.MeshVisualizer +{ + public enum BedShape { Rectangular, Circular }; + + public class DrawGlContentEventArgs : EventArgs + { + public bool ZBuffered { get; } + + public DrawGlContentEventArgs(bool zBuffered) + { + ZBuffered = zBuffered; + } + } + + public class MeshViewerWidget : GuiWidget + { + static public ImageBuffer BedImage = null; + public List interactionVolumes = new List(); + public InteractionVolume SelectedInteractionVolume { get; set; } = null; + public bool MouseDownOnInteractionVolume { get { return SelectedInteractionVolume != null; } } + + public PartProcessingInfo partProcessingInfo; + private static ImageBuffer lastCreatedBedImage = new ImageBuffer(); + + private static Dictionary materialColors = new Dictionary(); + private RGBA_Bytes bedBaseColor = new RGBA_Bytes(245, 245, 255); + static public Vector2 BedCenter { get; private set; } + private RGBA_Bytes bedMarkingsColor = RGBA_Bytes.Black; + private static BedShape bedShape = BedShape.Rectangular; + private static Mesh buildVolume = null; + private static Vector3 displayVolume; + private static Mesh printerBed = null; + private RenderTypes renderType = RenderTypes.Shaded; + + public double SnapGridDistance { get; set; } = 1; + + private TrackballTumbleWidget trackballTumbleWidget; + + private int volumeIndexWithMouseDown = -1; + + public MeshViewerWidget(Vector3 displayVolume, Vector2 bedCenter, BedShape bedShape, string startingTextMessage = "") + { + Scene.SelectionChanged += (sender, e) => + { + Invalidate(); + }; + RenderType = RenderTypes.Shaded; + RenderBed = true; + RenderBuildVolume = false; + //SetMaterialColor(1, RGBA_Bytes.LightGray, RGBA_Bytes.White); + BedColor = new RGBA_Floats(.8, .8, .8, .7).GetAsRGBA_Bytes(); + BuildVolumeColor = new RGBA_Floats(.2, .8, .3, .2).GetAsRGBA_Bytes(); + + trackballTumbleWidget = new TrackballTumbleWidget(this.World); + trackballTumbleWidget.DrawRotationHelperCircle = false; + trackballTumbleWidget.DrawGlContent += trackballTumbleWidget_DrawGlContent; + trackballTumbleWidget.TransformState = TrackBallController.MouseDownType.Rotation; + + AddChild(trackballTumbleWidget); + + CreatePrintBed(displayVolume, bedCenter, bedShape); + + trackballTumbleWidget.AnchorAll(); + + partProcessingInfo = new PartProcessingInfo(startingTextMessage); + + GuiWidget labelContainer = new GuiWidget(); + labelContainer.AnchorAll(); + labelContainer.AddChild(partProcessingInfo); + labelContainer.Selectable = false; + + SetMaterialColor(1, ActiveTheme.Instance.PrimaryAccentColor); + + this.AddChild(labelContainer); + } + + public WorldView World { get; } = new WorldView(0, 0); + + public event EventHandler LoadDone; + + public bool AllowBedRenderingWhenEmpty { get; set; } + + public RGBA_Bytes BedColor { get; set; } + + public RGBA_Bytes BuildVolumeColor { get; set; } + + public Vector3 DisplayVolume { get { return displayVolume; } } + + public static AxisAlignedBoundingBox GetAxisAlignedBoundingBox(List meshGroups) + { + AxisAlignedBoundingBox totalMeshBounds = AxisAlignedBoundingBox.Empty; + bool first = true; + foreach (MeshGroup meshGroup in meshGroups) + { + AxisAlignedBoundingBox meshBounds = meshGroup.GetAxisAlignedBoundingBox(); + if (first) + { + totalMeshBounds = meshBounds; + first = false; + } + else + { + totalMeshBounds = AxisAlignedBoundingBox.Union(totalMeshBounds, meshBounds); + } + } + + return totalMeshBounds; + } + + public override void OnLoad(EventArgs args) + { + // some debug code to be able to click on parts + if (false) + { + AfterDraw += (sender, e) => + { + foreach (var child in Scene.Children) + { + this.World.RenderDebugAABB(e.graphics2D, child.TraceData().GetAxisAlignedBoundingBox()); + this.World.RenderDebugAABB(e.graphics2D, child.GetAxisAlignedBoundingBox(Matrix4X4.Identity)); + } + }; + } + + base.OnLoad(args); + } + + + public override void FindNamedChildrenRecursive(string nameToSearchFor, List foundChildren, RectangleDouble touchingBounds, SearchType seachType, bool allowInvalidItems = true) + { + foreach (var child in Scene.Children) + { + string object3DName = child.Name; + if (object3DName == null && child.MeshPath != null) + { + object3DName = Path.GetFileName(child.MeshPath); + } + + bool nameFound = false; + + if (seachType == SearchType.Exact) + { + if (object3DName == nameToSearchFor) + { + nameFound = true; + } + } + else + { + if (nameToSearchFor == "" + || object3DName.Contains(nameToSearchFor)) + { + nameFound = true; + } + } + + if (nameFound) + { + AxisAlignedBoundingBox bounds = child.TraceData().GetAxisAlignedBoundingBox(); + + RectangleDouble screenBoundsOfObject3D = RectangleDouble.ZeroIntersection; + for(int i=0; i<4; i++) + { + screenBoundsOfObject3D.ExpandToInclude(this.World.GetScreenPosition(bounds.GetTopCorner(i))); + screenBoundsOfObject3D.ExpandToInclude(this.World.GetScreenPosition(bounds.GetBottomCorner(i))); + } + + if (touchingBounds.IsTouching(screenBoundsOfObject3D)) + { + Vector3 renderPosition = bounds.Center; + Vector2 objectCenterScreenSpace = this.World.GetScreenPosition(renderPosition); + Point2D screenPositionOfObject3D = new Point2D((int)objectCenterScreenSpace.x, (int)objectCenterScreenSpace.y); + + foundChildren.Add(new WidgetAndPosition(this, screenPositionOfObject3D, object3DName)); + } + } + } + + base.FindNamedChildrenRecursive(nameToSearchFor, foundChildren, touchingBounds, seachType, allowInvalidItems); + } + + public InteractiveScene Scene { get; } = new InteractiveScene(); + + public Mesh PrinterBed { get { return printerBed; } } + + public bool RenderBed { get; set; } + + public bool RenderBuildVolume { get; set; } + + public RenderTypes RenderType + { + get { return renderType; } + set + { + if (renderType != value) + { + renderType = value; + foreach(var renderTransfrom in Scene.VisibleMeshes(Matrix4X4.Identity)) + { + renderTransfrom.MeshData.MarkAsChanged(); + } + } + } + } + + public TrackballTumbleWidget TrackballTumbleWidget + { + get + { + return trackballTumbleWidget; + } + } + + public static void AssertDebugNotDefined() + { +#if DEBUG + throw new Exception("DEBUG is defined and should not be!"); +#endif + } + + public static RGBA_Bytes GetMaterialColor(int materialIndexBase1) + { + lock (materialColors) + { + if (materialColors.ContainsKey(materialIndexBase1)) + { + return materialColors[materialIndexBase1]; + } + } + + // we currently expect at most 4 extruders + return RGBA_Floats.FromHSL((materialIndexBase1 % 4) / 4.0, .5, .5).GetAsRGBA_Bytes(); + } + + public static RGBA_Bytes GetSelectedMaterialColor(int materialIndexBase1) + { + double hue0To1; + double saturation0To1; + double lightness0To1; + GetMaterialColor(materialIndexBase1).GetAsRGBA_Floats().GetHSL(out hue0To1, out saturation0To1, out lightness0To1); + + // now make it a bit lighter and less saturated + saturation0To1 = Math.Min(1, saturation0To1 * 2); + lightness0To1 = Math.Min(1, lightness0To1 * 1.2); + + // we sort of expect at most 4 extruders + return RGBA_Floats.FromHSL(hue0To1, saturation0To1, lightness0To1).GetAsRGBA_Bytes(); + } + + public static void SetMaterialColor(int materialIndexBase1, RGBA_Bytes color) + { + lock (materialColors) + { + if (!materialColors.ContainsKey(materialIndexBase1)) + { + materialColors.Add(materialIndexBase1, color); + } + else + { + materialColors[materialIndexBase1] = color; + } + } + } + + public void CreateGlDataObject(IObject3D item) + { + if(item.Mesh != null) + { + GLMeshTrianglePlugin.Get(item.Mesh); + } + + foreach (IObject3D child in item.Children.Where(o => o.Mesh != null)) + { + GLMeshTrianglePlugin.Get(child.Mesh); + } + } + + public void CreatePrintBed(Vector3 displayVolume, Vector2 bedCenter, BedShape bedShape) + { + if (MeshViewerWidget.BedCenter == bedCenter + && MeshViewerWidget.bedShape == bedShape + && MeshViewerWidget.displayVolume == displayVolume) + { + return; + } + + MeshViewerWidget.BedCenter = bedCenter; + MeshViewerWidget.bedShape = bedShape; + MeshViewerWidget.displayVolume = displayVolume; + Vector3 displayVolumeToBuild = Vector3.ComponentMax(displayVolume, new Vector3(1, 1, 1)); + + double sizeForMarking = Math.Max(displayVolumeToBuild.x, displayVolumeToBuild.y); + double divisor = 10; + int skip = 1; + if (sizeForMarking > 1000) + { + divisor = 100; + skip = 10; + } + else if (sizeForMarking > 300) + { + divisor = 50; + skip = 5; + } + + switch (bedShape) + { + case BedShape.Rectangular: + if (displayVolumeToBuild.z > 0) + { + buildVolume = PlatonicSolids.CreateCube(displayVolumeToBuild); + foreach (Vertex vertex in buildVolume.Vertices) + { + vertex.Position = vertex.Position + new Vector3(0, 0, displayVolumeToBuild.z / 2); + } + } + CreateRectangularBedGridImage(displayVolumeToBuild, bedCenter, divisor, skip); + printerBed = PlatonicSolids.CreateCube(displayVolumeToBuild.x, displayVolumeToBuild.y, 1.8); + { + Face face = printerBed.Faces[0]; + MeshHelper.PlaceTextureOnFace(face, BedImage); + } + break; + + case BedShape.Circular: + { + if (displayVolumeToBuild.z > 0) + { + buildVolume = VertexSourceToMesh.Extrude(new Ellipse(new Vector2(), displayVolumeToBuild.x / 2, displayVolumeToBuild.y / 2), displayVolumeToBuild.z); + foreach (Vertex vertex in buildVolume.Vertices) + { + vertex.Position = vertex.Position + new Vector3(0, 0, .2); + } + } + CreateCircularBedGridImage((int)(displayVolumeToBuild.x / divisor), (int)(displayVolumeToBuild.y / divisor), skip); + printerBed = VertexSourceToMesh.Extrude(new Ellipse(new Vector2(), displayVolumeToBuild.x / 2, displayVolumeToBuild.y / 2), 2); + { + foreach (Face face in printerBed.Faces) + { + if (face.normal.z > 0) + { + FaceTextureData faceData = FaceTextureData.Get(face); + faceData.Textures.Add(BedImage); + foreach (FaceEdge faceEdge in face.FaceEdges()) + { + FaceEdgeTextureUvData edgeUV = FaceEdgeTextureUvData.Get(faceEdge); + edgeUV.TextureUV.Add(new Vector2((displayVolumeToBuild.x / 2 + faceEdge.firstVertex.Position.x) / displayVolumeToBuild.x, + (displayVolumeToBuild.y / 2 + faceEdge.firstVertex.Position.y) / displayVolumeToBuild.y)); + } + } + } + } + } + break; + + default: + throw new NotImplementedException(); + } + + foreach (Vertex vertex in printerBed.Vertices) + { + vertex.Position = vertex.Position - new Vector3(-bedCenter, 2.2); + } + + if (buildVolume != null) + { + foreach (Vertex vertex in buildVolume.Vertices) + { + vertex.Position = vertex.Position - new Vector3(-bedCenter, 2.2); + } + } + + Invalidate(); + } + + public bool SuppressUiVolumes { get; set; } = false; + + public async Task LoadItemIntoScene(string itemPath, Vector2 bedCenter = new Vector2(), string itemName = null) + { + if (File.Exists(itemPath)) + { + BeginProgressReporting("Loading Mesh"); + + // TODO: How to we handle mesh load errors? How do we report success? + IObject3D loadedItem = await Task.Run(() => Object3D.Load(itemPath, progress: ReportProgress0to100)); + if (loadedItem != null) + { + if (itemName != null) + { + loadedItem.Name = itemName; + } + + // SetMeshAfterLoad + Scene.ModifyChildren(children => + { + if (loadedItem.Mesh != null) + { + // STLs currently load directly into the mesh rather than as a group like AMF + children.Add(loadedItem); + } + else + { + children.AddRange(loadedItem.Children); + } + }); + + CreateGlDataObject(loadedItem); + } + else + { + partProcessingInfo.centeredInfoText.Text = string.Format("Sorry! No 3D view available\nfor this file."); + } + + EndProgressReporting(); + + // Invoke LoadDone event + LoadDone?.Invoke(this, null); + } + else + { + partProcessingInfo.centeredInfoText.Text = string.Format("{0}\n'{1}'", "File not found on disk.", Path.GetFileName(itemPath)); + } + } + + public override void OnDraw(Graphics2D graphics2D) + { + base.OnDraw(graphics2D); + + //if (!SuppressUiVolumes) + { + foreach (InteractionVolume interactionVolume in interactionVolumes) + { + interactionVolume.Draw2DContent(graphics2D); + } + } + } + + public override void OnMouseDown(MouseEventArgs mouseEvent) + { + base.OnMouseDown(mouseEvent); + + if (trackballTumbleWidget.MouseCaptured) + { + if (trackballTumbleWidget.TransformState == TrackBallController.MouseDownType.Rotation || mouseEvent.Button == MouseButtons.Right) + { + trackballTumbleWidget.DrawRotationHelperCircle = true; + } + } + + int volumeHitIndex; + Ray ray = this.World.GetRayForLocalBounds(mouseEvent.Position); + IntersectInfo info; + if (this.Scene.HasSelection + && !SuppressUiVolumes + && FindInteractionVolumeHit(ray, out volumeHitIndex, out info)) + { + MouseEvent3DArgs mouseEvent3D = new MouseEvent3DArgs(mouseEvent, ray, info); + volumeIndexWithMouseDown = volumeHitIndex; + interactionVolumes[volumeHitIndex].OnMouseDown(mouseEvent3D); + SelectedInteractionVolume = interactionVolumes[volumeHitIndex]; + } + else + { + SelectedInteractionVolume = null; + } + } + + public override void OnMouseMove(MouseEventArgs mouseEvent) + { + base.OnMouseMove(mouseEvent); + + if (SuppressUiVolumes) + { + return; + } + + Ray ray = this.World.GetRayForLocalBounds(mouseEvent.Position); + IntersectInfo info = null; + if (MouseDownOnInteractionVolume && volumeIndexWithMouseDown != -1) + { + MouseEvent3DArgs mouseEvent3D = new MouseEvent3DArgs(mouseEvent, ray, info); + interactionVolumes[volumeIndexWithMouseDown].OnMouseMove(mouseEvent3D); + } + else + { + MouseEvent3DArgs mouseEvent3D = new MouseEvent3DArgs(mouseEvent, ray, info); + + int volumeHitIndex; + FindInteractionVolumeHit(ray, out volumeHitIndex, out info); + + for (int i = 0; i < interactionVolumes.Count; i++) + { + if (i == volumeHitIndex) + { + interactionVolumes[i].MouseOver = true; + interactionVolumes[i].MouseMoveInfo = info; + } + else + { + interactionVolumes[i].MouseOver = false; + interactionVolumes[i].MouseMoveInfo = null; + } + + interactionVolumes[i].OnMouseMove(mouseEvent3D); + } + } + } + + public override void OnMouseUp(MouseEventArgs mouseEvent) + { + trackballTumbleWidget.DrawRotationHelperCircle = false; + Invalidate(); + + if(SuppressUiVolumes) + { + return; + } + + int volumeHitIndex; + Ray ray = this.World.GetRayForLocalBounds(mouseEvent.Position); + IntersectInfo info; + bool anyInteractionVolumeHit = FindInteractionVolumeHit(ray, out volumeHitIndex, out info); + MouseEvent3DArgs mouseEvent3D = new MouseEvent3DArgs(mouseEvent, ray, info); + + if (MouseDownOnInteractionVolume && volumeIndexWithMouseDown != -1) + { + interactionVolumes[volumeIndexWithMouseDown].OnMouseUp(mouseEvent3D); + SelectedInteractionVolume = null; + + volumeIndexWithMouseDown = -1; + } + else + { + volumeIndexWithMouseDown = -1; + + if (anyInteractionVolumeHit) + { + interactionVolumes[volumeHitIndex].OnMouseUp(mouseEvent3D); + } + SelectedInteractionVolume = null; + } + + base.OnMouseUp(mouseEvent); + } + + public void ResetView() + { + trackballTumbleWidget.ZeroVelocity(); + + this.World.Reset(); + this.World.Scale = .03; + this.World.Translate(-new Vector3(BedCenter)); + this.World.Rotate(Quaternion.FromEulerAngles(new Vector3(0, 0, MathHelper.Tau / 16))); + this.World.Rotate(Quaternion.FromEulerAngles(new Vector3(-MathHelper.Tau * .19, 0, 0))); + } + + private void CreateCircularBedGridImage(int linesInX, int linesInY, int increment = 1) + { + Vector2 bedImageCentimeters = new Vector2(linesInX, linesInY); + BedImage = new ImageBuffer(1024, 1024); + Graphics2D graphics2D = BedImage.NewGraphics2D(); + graphics2D.Clear(bedBaseColor); + { + double lineDist = BedImage.Width / (double)linesInX; + + int count = 1; + int pointSize = 16; + graphics2D.DrawString(count.ToString(), 4, 4, pointSize, color: bedMarkingsColor); + double currentRadius = lineDist; + Vector2 bedCenter = new Vector2(BedImage.Width / 2, BedImage.Height / 2); + for (double linePos = lineDist + BedImage.Width / 2; linePos < BedImage.Width; linePos += lineDist) + { + int linePosInt = (int)linePos; + graphics2D.DrawString((count * increment).ToString(), linePos + 2, BedImage.Height / 2, pointSize, color: bedMarkingsColor); + + Ellipse circle = new Ellipse(bedCenter, currentRadius); + Stroke outline = new Stroke(circle); + graphics2D.Render(outline, bedMarkingsColor); + currentRadius += lineDist; + count++; + } + + graphics2D.Line(0, BedImage.Height / 2, BedImage.Width, BedImage.Height / 2, bedMarkingsColor); + graphics2D.Line(BedImage.Width / 2, 0, BedImage.Width / 2, BedImage.Height, bedMarkingsColor); + } + } + + private void CreateRectangularBedGridImage(Vector3 displayVolumeToBuild, Vector2 bedCenter, double divisor, double skip) + { + lock (lastCreatedBedImage) + { + BedImage = new ImageBuffer(1024, 1024); + Graphics2D graphics2D = BedImage.NewGraphics2D(); + graphics2D.Clear(bedBaseColor); + { + double lineDist = BedImage.Width / (displayVolumeToBuild.x / divisor); + + double xPositionCm = (-(displayVolume.x / 2.0) + bedCenter.x) / divisor; + int xPositionCmInt = (int)Math.Round(xPositionCm); + double fraction = xPositionCm - xPositionCmInt; + int pointSize = 20; + graphics2D.DrawString((xPositionCmInt * skip).ToString(), 4, 4, pointSize, color: bedMarkingsColor); + for (double linePos = lineDist * (1 - fraction); linePos < BedImage.Width; linePos += lineDist) + { + xPositionCmInt++; + int linePosInt = (int)linePos; + int lineWidth = 1; + if (xPositionCmInt == 0) + { + lineWidth = 2; + } + graphics2D.Line(linePosInt, 0, linePosInt, BedImage.Height, bedMarkingsColor, lineWidth); + graphics2D.DrawString((xPositionCmInt * skip).ToString(), linePos + 4, 4, pointSize, color: bedMarkingsColor); + } + } + { + double lineDist = BedImage.Height / (displayVolumeToBuild.y / divisor); + + double yPositionCm = (-(displayVolume.y / 2.0) + bedCenter.y) / divisor; + int yPositionCmInt = (int)Math.Round(yPositionCm); + double fraction = yPositionCm - yPositionCmInt; + int pointSize = 20; + for (double linePos = lineDist * (1 - fraction); linePos < BedImage.Height; linePos += lineDist) + { + yPositionCmInt++; + int linePosInt = (int)linePos; + int lineWidth = 1; + if (yPositionCmInt == 0) + { + lineWidth = 2; + } + graphics2D.Line(0, linePosInt, BedImage.Height, linePosInt, bedMarkingsColor, lineWidth); + + graphics2D.DrawString((yPositionCmInt * skip).ToString(), 4, linePos + 4, pointSize, color: bedMarkingsColor); + } + } + + lastCreatedBedImage = BedImage; + } + } + + private bool FindInteractionVolumeHit(Ray ray, out int interactionVolumeHitIndex, out IntersectInfo info) + { + interactionVolumeHitIndex = -1; + if (interactionVolumes.Count == 0 || interactionVolumes[0].CollisionVolume == null) + { + info = null; + return false; + } + + List uiTraceables = new List(); + foreach (InteractionVolume interactionVolume in interactionVolumes) + { + if (interactionVolume.CollisionVolume != null) + { + IPrimitive traceData = interactionVolume.CollisionVolume; + uiTraceables.Add(new Transform(traceData, interactionVolume.TotalTransform)); + } + } + IPrimitive allUiObjects = BoundingVolumeHierarchy.CreateNewHierachy(uiTraceables); + + info = allUiObjects.GetClosestIntersection(ray); + if (info != null) + { + for (int i = 0; i < interactionVolumes.Count; i++) + { + List insideBounds = new List(); + if (interactionVolumes[i].CollisionVolume != null) + { + interactionVolumes[i].CollisionVolume.GetContained(insideBounds, info.closestHitObject.GetAxisAlignedBoundingBox()); + if (insideBounds.Contains(info.closestHitObject)) + { + interactionVolumeHitIndex = i; + return true; + } + } + } + } + + return false; + } + + private string progressReportingPrimaryTask = ""; + + public void BeginProgressReporting(string taskDescription) + { + progressReportingPrimaryTask = taskDescription; + + partProcessingInfo.Visible = true; + partProcessingInfo.progressControl.PercentComplete = 0; + partProcessingInfo.centeredInfoText.Text = taskDescription + "..."; + } + + public void EndProgressReporting() + { + progressReportingPrimaryTask = ""; + partProcessingInfo.Visible = false; + } + + public void ReportProgress0to100(double progress0To1, string processingState, out bool continueProcessing) + { + if (this.HasBeenClosed) + { + continueProcessing = false; + } + else + { + continueProcessing = true; + } + + UiThread.RunOnIdle(() => + { + int percentComplete = (int)(progress0To1 * 100); + partProcessingInfo.centeredInfoText.Text = "{0} {1}%...".FormatWith(progressReportingPrimaryTask, percentComplete); + partProcessingInfo.progressControl.PercentComplete = percentComplete; + + // Only assign to textbox if value passed through + if (processingState != null) + { + partProcessingInfo.centeredInfoDescription.Text = processingState; + } + }); + } + + private void DrawObject(IObject3D object3D, Matrix4X4 transform, bool parentSelected) + { + foreach(MeshAndTransform meshAndTransform in object3D.VisibleMeshes(transform)) + { + bool isSelected = parentSelected || + Scene.HasSelection && (object3D == Scene.SelectedItem || Scene.SelectedItem.Children.Contains(object3D)); + + MeshMaterialData meshData = MeshMaterialData.Get(meshAndTransform.MeshData); + RGBA_Bytes drawColor = object3D.Color; + if (drawColor.Alpha0To1 == 0) + { + drawColor = isSelected ? GetSelectedMaterialColor(meshData.MaterialIndex) : GetMaterialColor(meshData.MaterialIndex); + } + + GLHelper.Render(meshAndTransform.MeshData, drawColor, meshAndTransform.Matrix, RenderType); + } + } + + private void trackballTumbleWidget_DrawGlContent(object sender, EventArgs e) + { + foreach(var object3D in Scene.Children) + { + DrawObject(object3D, Matrix4X4.Identity, false); + } + + if (RenderBed) + { + GLHelper.Render(printerBed, this.BedColor); + } + + if (buildVolume != null && RenderBuildVolume) + { + GLHelper.Render(buildVolume, this.BuildVolumeColor); + } + + // we don't want to render the bed or build volume before we load a model. + if (Scene.HasChildren || AllowBedRenderingWhenEmpty) + { + if (false) // this is code to draw a small axis indicator + { + double big = 10; + double small = 1; + Mesh xAxis = PlatonicSolids.CreateCube(big, small, small); + GLHelper.Render(xAxis, RGBA_Bytes.Red); + Mesh yAxis = PlatonicSolids.CreateCube(small, big, small); + GLHelper.Render(yAxis, RGBA_Bytes.Green); + Mesh zAxis = PlatonicSolids.CreateCube(small, small, big); + GLHelper.Render(zAxis, RGBA_Bytes.Blue); + } + } + + DrawInteractionVolumes(e); + } + + private void DrawInteractionVolumes(EventArgs e) + { + if(SuppressUiVolumes) + { + return; + } + + // draw on top of anything that is already drawn + foreach (InteractionVolume interactionVolume in interactionVolumes) + { + if (interactionVolume.DrawOnTop) + { + GL.Disable(EnableCap.DepthTest); + interactionVolume.DrawGlContent(new DrawGlContentEventArgs(false)); + GL.Enable(EnableCap.DepthTest); + } + } + + // Draw again setting the depth buffer and ensuring that all the interaction objects are sorted as well as we can + foreach (InteractionVolume interactionVolume in interactionVolumes) + { + interactionVolume.DrawGlContent(new DrawGlContentEventArgs(true)); + } + } + + public class PartProcessingInfo : FlowLayoutWidget + { + internal TextWidget centeredInfoDescription; + internal TextWidget centeredInfoText; + internal ProgressControl progressControl; + + internal PartProcessingInfo(string startingTextMessage) + : base(FlowDirection.TopToBottom) + { + progressControl = new ProgressControl("", RGBA_Bytes.Black, RGBA_Bytes.Black); + progressControl.HAnchor = HAnchor.ParentCenter; + AddChild(progressControl); + progressControl.Visible = false; + progressControl.ProgressChanged += (sender, e) => + { + progressControl.Visible = true; + }; + + centeredInfoText = new TextWidget(startingTextMessage); + centeredInfoText.HAnchor = HAnchor.ParentCenter; + centeredInfoText.AutoExpandBoundsToText = true; + AddChild(centeredInfoText); + + centeredInfoDescription = new TextWidget(""); + centeredInfoDescription.HAnchor = HAnchor.ParentCenter; + centeredInfoDescription.AutoExpandBoundsToText = true; + AddChild(centeredInfoDescription); + + VAnchor |= VAnchor.ParentCenter; + HAnchor |= HAnchor.ParentCenter; + } + } + } +} \ No newline at end of file diff --git a/PartPreviewWindow/View3D/MouseEvent3DArgs.cs b/PartPreviewWindow/View3D/MouseEvent3DArgs.cs new file mode 100644 index 000000000..307e9caf1 --- /dev/null +++ b/PartPreviewWindow/View3D/MouseEvent3DArgs.cs @@ -0,0 +1,52 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.Agg.UI; +using MatterHackers.RayTracer; +using MatterHackers.VectorMath; +using System; + +namespace MatterHackers.MeshVisualizer +{ + public class MouseEvent3DArgs : EventArgs + { + public IntersectInfo info; + public MouseEventArgs MouseEvent2D; + private Ray mouseRay; + + public Ray MouseRay { get { return mouseRay; } } + + public MouseEvent3DArgs(MouseEventArgs mouseEvent2D, Ray mouseRay, IntersectInfo info) + { + this.info = info; + this.MouseEvent2D = mouseEvent2D; + this.mouseRay = mouseRay; + } + } +} \ No newline at end of file diff --git a/PartPreviewWindow/ViewGcodeBasic.cs b/PartPreviewWindow/ViewGcodeBasic.cs index 6cce66ec6..e6e04679d 100644 --- a/PartPreviewWindow/ViewGcodeBasic.cs +++ b/PartPreviewWindow/ViewGcodeBasic.cs @@ -28,7 +28,6 @@ either expressed or implied, of the FreeBSD Project. */ using System; -using System.ComponentModel; using System.IO; using MatterHackers.Agg; using MatterHackers.Agg.UI; @@ -434,7 +433,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow GetRenderType(), gcodeViewWidget.FeatureToStartOnRatio0To1, gcodeViewWidget.FeatureToEndOnRatio0To1, - new Vector2[] { ActiveSliceSettings.Instance.Helpers.ExtruderOffset(0), ActiveSliceSettings.Instance.Helpers.ExtruderOffset(1) }); + new Vector2[] { ActiveSliceSettings.Instance.Helpers.ExtruderOffset(0), ActiveSliceSettings.Instance.Helpers.ExtruderOffset(1) }, + MeshViewerWidget.GetMaterialColor); gcodeViewWidget.gCodeRenderer.Render3D(renderInfo); } diff --git a/PartPreviewWindow/ViewGcodeWidget.cs b/PartPreviewWindow/ViewGcodeWidget.cs index f36900bb3..2e53ee268 100644 --- a/PartPreviewWindow/ViewGcodeWidget.cs +++ b/PartPreviewWindow/ViewGcodeWidget.cs @@ -27,20 +27,20 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +using System; +using System.Diagnostics; +using System.Threading.Tasks; using MatterHackers.Agg; using MatterHackers.Agg.Transform; using MatterHackers.Agg.UI; using MatterHackers.Agg.VertexSource; using MatterHackers.GCodeVisualizer; +using MatterHackers.Localizations; using MatterHackers.MatterControl.SlicerConfiguration; +using MatterHackers.MeshVisualizer; using MatterHackers.RenderOpenGl; using MatterHackers.RenderOpenGl.OpenGl; using MatterHackers.VectorMath; -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Threading.Tasks; -using MatterHackers.Localizations; namespace MatterHackers.MatterControl.PartPreviewWindow { @@ -299,7 +299,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow GCodeRenderInfo renderInfo = new GCodeRenderInfo(activeLayerIndex, activeLayerIndex, transform, layerScale, CreateRenderInfo(), FeatureToStartOnRatio0To1, FeatureToEndOnRatio0To1, - new Vector2[] { ActiveSliceSettings.Instance.Helpers.ExtruderOffset(0), ActiveSliceSettings.Instance.Helpers.ExtruderOffset(1) }); + new Vector2[] { ActiveSliceSettings.Instance.Helpers.ExtruderOffset(0), ActiveSliceSettings.Instance.Helpers.ExtruderOffset(1) }, + MeshViewerWidget.GetMaterialColor); //using (new PerformanceTimer("GCode Timer", "Render")) { diff --git a/Tests/MatterControl.Tests/MatterControl.Tests.csproj b/Tests/MatterControl.Tests/MatterControl.Tests.csproj index d3d7306ac..ea11d77b9 100644 --- a/Tests/MatterControl.Tests/MatterControl.Tests.csproj +++ b/Tests/MatterControl.Tests/MatterControl.Tests.csproj @@ -83,6 +83,10 @@ {0B8D6F56-BD7F-4426-B858-D9292B084656} MatterControl + + {97d5ade3-c1b4-4b46-8a3e-718a4f7f079f} + MatterControl.Printing + {bb58ca42-991b-41b7-bde7-dcd2911df8b9} PrinterEmulator @@ -95,14 +99,6 @@ {04667764-dc7b-4b95-aef6-b4e6c87a54e9} DataConverters3D - - {F67AE800-B0C7-42A8-836F-597B4E74591C} - GCodeVisualizer - - - {a737bc76-165b-46c6-82b7-8871c7c92942} - MeshViewer - {E9102310-0029-4D8F-B1E9-88FBA6147D45} GuiAutomation diff --git a/TextCreator/TextCreator.csproj b/TextCreator/TextCreator.csproj index 1f0549b70..253db1bc5 100644 --- a/TextCreator/TextCreator.csproj +++ b/TextCreator/TextCreator.csproj @@ -166,10 +166,6 @@ {04667764-DC7B-4B95-AEF6-B4E6C87A54E9} DataConverters3D - - {A737BC76-165B-46C6-82B7-8871C7C92942} - MeshViewer - {74F6BB6C-9D02-4512-A59A-21940E35C532} Gui