public bool RemovePerimeterOverlaps(List <Point3> perimeter, long overlapMergeAmount_um, out List <PathAndWidth> separatedPolygons, bool pathIsClosed = true)
        {
            bool pathWasOptomized = false;

            perimeter = MakeCloseSegmentsMergable(perimeter, overlapMergeAmount_um);

            // make a copy that has every point duplicated (so that we have them as segments).
            List <Segment> polySegments = Segment.ConvertPathToSegments(perimeter, pathIsClosed);

            Altered[] markedAltered = new Altered[polySegments.Count];

            int segmentCount = polySegments.Count;

            // now walk every segment and check if there is another segment that is similar enough to merge them together
            for (int firstSegmentIndex = 0; firstSegmentIndex < segmentCount; firstSegmentIndex++)
            {
                for (int checkSegmentIndex = firstSegmentIndex + 1; checkSegmentIndex < segmentCount; checkSegmentIndex++)
                {
                    // The first point of start and the last point of check (the path will be coming back on itself).
                    long startDelta = (polySegments[firstSegmentIndex].Start - polySegments[checkSegmentIndex].End).Length();
                    // if the segments are similar enough
                    if (startDelta < overlapMergeAmount_um)
                    {
                        // The last point of start and the first point of check (the path will be coming back on itself).
                        long endDelta = (polySegments[firstSegmentIndex].End - polySegments[checkSegmentIndex].Start).Length();
                        if (endDelta < overlapMergeAmount_um)
                        {
                            pathWasOptomized = true;
                            // move the first segments points to the average of the merge positions
                            long startEndWidth = Math.Abs((polySegments[firstSegmentIndex].Start - polySegments[checkSegmentIndex].End).Length());
                            long endStartWidth = Math.Abs((polySegments[firstSegmentIndex].End - polySegments[checkSegmentIndex].Start).Length());
                            polySegments[firstSegmentIndex].Width = Math.Min(startEndWidth, endStartWidth);
                            polySegments[firstSegmentIndex].Start = (polySegments[firstSegmentIndex].Start + polySegments[checkSegmentIndex].End) / 2;                           // the start
                            polySegments[firstSegmentIndex].End   = (polySegments[firstSegmentIndex].End + polySegments[checkSegmentIndex].Start) / 2;                           // the end

                            markedAltered[firstSegmentIndex] = Altered.merged;
                            // mark this segment for removal
                            markedAltered[checkSegmentIndex] = Altered.remove;
                            // We only expect to find one match for each segment, so move on to the next segment
                            break;
                        }
                    }
                }
            }

            // remove the marked segments
            for (int segmentIndex = segmentCount - 1; segmentIndex >= 0; segmentIndex--)
            {
                if (markedAltered[segmentIndex] == Altered.remove)
                {
                    polySegments.RemoveAt(segmentIndex);
                }
            }

            // go through the polySegments and create a new polygon for every connected set of segments
            separatedPolygons = new List <PathAndWidth>();
            PathAndWidth currentPolygon = new PathAndWidth();

            separatedPolygons.Add(currentPolygon);
            // put in the first point
            for (int segmentIndex = 0; segmentIndex < polySegments.Count; segmentIndex++)
            {
                // add the start point
                currentPolygon.Path.Add(polySegments[segmentIndex].Start);
                currentPolygon.ExtrusionWidthUm = polySegments[segmentIndex].Width + overlapMergeAmount_um;

                // if the next segment is not connected to this one
                if (segmentIndex < polySegments.Count - 1 &&
                    (polySegments[segmentIndex].End != polySegments[segmentIndex + 1].Start ||
                     polySegments[segmentIndex].Width != polySegments[segmentIndex + 1].Width))
                {
                    // add the end point
                    currentPolygon.Path.Add(polySegments[segmentIndex].End);

                    // create a new polygon
                    currentPolygon = new PathAndWidth();
                    separatedPolygons.Add(currentPolygon);
                }
            }

            // add the end point
            currentPolygon.Path.Add(polySegments[polySegments.Count - 1].End);

            return(pathWasOptomized);
        }
        public void WriteQueuedGCode(int layerThickness, int fanSpeedPercent = -1, int bridgeFanSpeedPercent = -1)
        {
            GCodePathConfig lastConfig    = null;
            int             extruderIndex = gcodeExport.GetExtruderIndex();

            for (int pathIndex = 0; pathIndex < paths.Count; pathIndex++)
            {
                GCodePath path = paths[pathIndex];
                if (extruderIndex != path.extruderIndex)
                {
                    extruderIndex = path.extruderIndex;
                    gcodeExport.SwitchExtruder(extruderIndex);
                }
                else if (path.Retract)
                {
                    gcodeExport.WriteRetraction();
                }
                if (path.config != travelConfig && lastConfig != path.config)
                {
                    if (path.config.gcodeComment == "BRIDGE" && bridgeFanSpeedPercent != -1)
                    {
                        gcodeExport.WriteFanCommand(bridgeFanSpeedPercent);
                    }
                    else if (lastConfig?.gcodeComment == "BRIDGE" && bridgeFanSpeedPercent != -1)
                    {
                        gcodeExport.WriteFanCommand(fanSpeedPercent);
                    }

                    gcodeExport.WriteComment("TYPE:{0}".FormatWith(path.config.gcodeComment));
                    lastConfig = path.config;
                }

                double speed = path.config.speed;

                if (path.config.lineWidth_um != 0)
                {
                    // Prevent cooling overrides from affecting bridge moves
                    if (path.config.gcodeComment != "BRIDGE")
                    {
                        speed = speed * extrudeSpeedFactor / 100;
                    }
                }
                else
                {
                    speed = speed * travelSpeedFactor / 100;
                }

                if (path.points.Count == 1 &&
                    path.config != travelConfig &&
                    (gcodeExport.GetPositionXY() - path.points[0].XYPoint).ShorterThen(path.config.lineWidth_um * 2))
                {
                    //Check for lots of small moves and combine them into one large line
                    Point3 nextPosition = path.points[0];
                    int    i            = pathIndex + 1;
                    while (i < paths.Count && paths[i].points.Count == 1 && (nextPosition - paths[i].points[0]).ShorterThen(path.config.lineWidth_um * 2))
                    {
                        nextPosition = paths[i].points[0];
                        i++;
                    }
                    if (paths[i - 1].config == travelConfig)
                    {
                        i--;
                    }

                    if (i > pathIndex + 2)
                    {
                        nextPosition = gcodeExport.GetPosition();
                        for (int x = pathIndex; x < i - 1; x += 2)
                        {
                            long   oldLen   = (nextPosition - paths[x].points[0]).Length();
                            Point3 newPoint = (paths[x].points[0] + paths[x + 1].points[0]) / 2;
                            long   newLen   = (gcodeExport.GetPosition() - newPoint).Length();
                            if (newLen > 0)
                            {
                                gcodeExport.WriteMove(newPoint, speed, (int)(path.config.lineWidth_um * oldLen / newLen));
                            }

                            nextPosition = paths[x + 1].points[0];
                        }

                        gcodeExport.WriteMove(paths[i - 1].points[0], speed, path.config.lineWidth_um);
                        pathIndex = i - 1;
                        continue;
                    }
                }


                bool spiralize = path.config.spiralize;
                if (spiralize)
                {
                    //Check if we are the last spiralize path in the list, if not, do not spiralize.
                    for (int m = pathIndex + 1; m < paths.Count; m++)
                    {
                        if (paths[m].config.spiralize)
                        {
                            spiralize = false;
                        }
                    }
                }

                if (spiralize)                 // if we are still in spiralize mode
                {
                    //If we need to spiralize then raise the head slowly by 1 layer as this path progresses.
                    double   totalLength     = 0;
                    long     z               = gcodeExport.GetPositionZ();
                    IntPoint currentPosition = gcodeExport.GetPositionXY();
                    for (int pointIndex = 0; pointIndex < path.points.Count; pointIndex++)
                    {
                        IntPoint nextPosition = path.points[pointIndex].XYPoint;
                        totalLength    += (currentPosition - nextPosition).LengthMm();
                        currentPosition = nextPosition;
                    }

                    double length = 0.0;
                    currentPosition = gcodeExport.GetPositionXY();
                    for (int i = 0; i < path.points.Count; i++)
                    {
                        IntPoint nextPosition = path.points[i].XYPoint;
                        length         += (currentPosition - nextPosition).LengthMm();
                        currentPosition = nextPosition;
                        Point3 nextExtrusion = path.points[i];
                        nextExtrusion.z = (int)(z + layerThickness * length / totalLength + .5);
                        gcodeExport.WriteMove(nextExtrusion, speed, path.config.lineWidth_um);
                    }
                }
                else
                {
                    bool pathIsClosed = true;
                    if (perimeterStartEndOverlapRatio < 1)
                    {
                        pathIsClosed = !TrimPerimeterIfNeeded(path, perimeterStartEndOverlapRatio);
                    }

                    // This is test code to remove double drawn small perimeter lines.
                    List <PathAndWidth> pathsWithOverlapsRemoved;
                    if (RemovePerimetersThatOverlap(path, speed, out pathsWithOverlapsRemoved, pathIsClosed))
                    {
                        for (int polygonIndex = 0; polygonIndex < pathsWithOverlapsRemoved.Count; polygonIndex++)
                        {
                            PathAndWidth polygon = pathsWithOverlapsRemoved[polygonIndex];

                            if (polygon.Path.Count == 2)
                            {
                                // make sure the path is ordered with the first point the closest to where we are now
                                Point3 currentPosition = gcodeExport.GetPosition();
                                // if the second point is closer swap them
                                if ((polygon.Path[1] - currentPosition).LengthSquared() < (polygon.Path[0] - currentPosition).LengthSquared())
                                {
                                    // swap them
                                    Point3 temp = polygon.Path[0];
                                    polygon.Path[0] = polygon.Path[1];
                                    polygon.Path[1] = temp;
                                }
                            }

                            // move to the start of this polygon
                            gcodeExport.WriteMove(polygon.Path[0], travelConfig.speed, 0);

                            // write all the data for the polygon
                            for (int pointIndex = 1; pointIndex < polygon.Path.Count; pointIndex++)
                            {
                                gcodeExport.WriteMove(polygon.Path[pointIndex], speed, polygon.ExtrusionWidthUm);
                            }
                        }
                    }
                    else
                    {
                        for (int i = 0; i < path.points.Count; i++)
                        {
                            gcodeExport.WriteMove(path.points[i], speed, path.config.lineWidth_um);
                        }
                    }
                }
            }

            gcodeExport.UpdateTotalPrintTime();
        }
        public bool RemovePerimeterOverlaps(List<Point3> perimeter, long overlapMergeAmount_um, out List<PathAndWidth> separatedPolygons, bool pathIsClosed = true)
        {
            bool pathWasOptomized = false;

            perimeter = MakeCloseSegmentsMergable(perimeter, overlapMergeAmount_um);

            // make a copy that has every point duplicated (so that we have them as segments).
            List <Segment> polySegments = Segment.ConvertPathToSegments(perimeter, pathIsClosed);

            Altered[] markedAltered = new Altered[polySegments.Count];

            int segmentCount = polySegments.Count;
            // now walk every segment and check if there is another segment that is similar enough to merge them together
            for (int firstSegmentIndex = 0; firstSegmentIndex < segmentCount; firstSegmentIndex++)
            {
                for (int checkSegmentIndex = firstSegmentIndex + 1; checkSegmentIndex < segmentCount; checkSegmentIndex++)
                {
                    // The first point of start and the last point of check (the path will be coming back on itself).
                    long startDelta = (polySegments[firstSegmentIndex].Start - polySegments[checkSegmentIndex].End).Length();
                    // if the segments are similar enough
                    if (startDelta < overlapMergeAmount_um)
                    {
                        // The last point of start and the first point of check (the path will be coming back on itself).
                        long endDelta = (polySegments[firstSegmentIndex].End - polySegments[checkSegmentIndex].Start).Length();
                        if (endDelta < overlapMergeAmount_um)
                        {
                            pathWasOptomized = true;
                            // move the first segments points to the average of the merge positions
                            long startEndWidth = Math.Abs((polySegments[firstSegmentIndex].Start - polySegments[checkSegmentIndex].End).Length());
                            long endStartWidth = Math.Abs((polySegments[firstSegmentIndex].End - polySegments[checkSegmentIndex].Start).Length());
                            polySegments[firstSegmentIndex].Width = Math.Min(startEndWidth, endStartWidth);
                            polySegments[firstSegmentIndex].Start = (polySegments[firstSegmentIndex].Start + polySegments[checkSegmentIndex].End) / 2; // the start
                            polySegments[firstSegmentIndex].End = (polySegments[firstSegmentIndex].End + polySegments[checkSegmentIndex].Start) / 2; // the end

                            markedAltered[firstSegmentIndex] = Altered.merged;
                            // mark this segment for removal
                            markedAltered[checkSegmentIndex] = Altered.remove;
                            // We only expect to find one match for each segment, so move on to the next segment
                            break;
                        }
                    }
                }
            }

            // remove the marked segments
            for (int segmentIndex = segmentCount - 1; segmentIndex >= 0; segmentIndex--)
            {
                if (markedAltered[segmentIndex] == Altered.remove)
                {
                    polySegments.RemoveAt(segmentIndex);
                }
            }

            // go through the polySegments and create a new polygon for every connected set of segments
            separatedPolygons = new List<PathAndWidth>();
            PathAndWidth currentPolygon = new PathAndWidth();
            separatedPolygons.Add(currentPolygon);
            // put in the first point
            for (int segmentIndex = 0; segmentIndex < polySegments.Count; segmentIndex++)
            {
                // add the start point
                currentPolygon.Path.Add(polySegments[segmentIndex].Start);
                currentPolygon.ExtrusionWidthUm = polySegments[segmentIndex].Width + overlapMergeAmount_um;

                // if the next segment is not connected to this one
                if (segmentIndex < polySegments.Count - 1
                    && (polySegments[segmentIndex].End != polySegments[segmentIndex + 1].Start
                    || polySegments[segmentIndex].Width != polySegments[segmentIndex + 1].Width))
                {
                    // add the end point
                    currentPolygon.Path.Add(polySegments[segmentIndex].End);

                    // create a new polygon
                    currentPolygon = new PathAndWidth();
                    separatedPolygons.Add(currentPolygon);
                }
            }

            // add the end point
            currentPolygon.Path.Add(polySegments[polySegments.Count - 1].End);

            return pathWasOptomized;
        }