public void CorrectSeamPlacement() { // coincident points return 0 angle { IntPoint p1 = new IntPoint(10, 0); IntPoint p2 = new IntPoint(0, 0); IntPoint p3 = new IntPoint(0, 0); Assert.IsTrue(IslandOrderOptimizer.GetTurnAmount(p1, p2, p3) == 0); } // no turn returns a 0 angle { IntPoint p1 = new IntPoint(10, 0); IntPoint p2 = new IntPoint(0, 0); IntPoint p3 = new IntPoint(-10, 0); Assert.IsTrue(IslandOrderOptimizer.GetTurnAmount(p1, p2, p3) == 0); } // 90 turn works { IntPoint p1 = new IntPoint(0, 0); IntPoint p2 = new IntPoint(10, 0); IntPoint p3 = new IntPoint(10, 10); Assert.AreEqual(IslandOrderOptimizer.GetTurnAmount(p1, p2, p3), Math.PI / 2, .001); IntPoint p4 = new IntPoint(0, 10); IntPoint p5 = new IntPoint(0, 0); IntPoint p6 = new IntPoint(10, 0); Assert.AreEqual(IslandOrderOptimizer.GetTurnAmount(p4, p5, p6), Math.PI / 2, .001); } // -90 turn works { IntPoint p1 = new IntPoint(0, 0); IntPoint p2 = new IntPoint(10, 0); IntPoint p3 = new IntPoint(10, -10); Assert.AreEqual(IslandOrderOptimizer.GetTurnAmount(p1, p2, p3), -Math.PI / 2, .001); } // 45 turn works { IntPoint p1 = new IntPoint(0, 0); IntPoint p2 = new IntPoint(10, 0); IntPoint p3 = new IntPoint(15, 5); Assert.AreEqual(Math.PI / 4, IslandOrderOptimizer.GetTurnAmount(p1, p2, p3), .001); IntPoint p4 = new IntPoint(0, 0); IntPoint p5 = new IntPoint(-10, 0); IntPoint p6 = new IntPoint(-15, -5); Assert.AreEqual(Math.PI / 4, IslandOrderOptimizer.GetTurnAmount(p4, p5, p6), .001); } // -45 turn works { IntPoint p1 = new IntPoint(0, 0); IntPoint p2 = new IntPoint(10, 0); IntPoint p3 = new IntPoint(15, -5); Assert.AreEqual(-Math.PI / 4, IslandOrderOptimizer.GetTurnAmount(p1, p2, p3), .001); } // find the right point wound ccw { // 4________3 // | / // | /2 // | \ // |0______\1 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(70, 50), new IntPoint(100, 100), new IntPoint(0, 100) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 2); } // find the right point wound ccw { // 3________2 // | | // | | // | | // |0______|1 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(100, 100), new IntPoint(0, 100) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 3); } // find the right point wound ccw { // 1________0 // | | // | | // | | // |2______|3 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(100, 0) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 1); } // find the right point wound cw { // 1________2 // | | // | | // | | // |0______|3 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(100, 0) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 1); } // find the right point wound cw { // 0________1 // | | // | | // | | // |3______|2 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(100, 0), new IntPoint(0, 0) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 0); } // find the right point wound ccw { // 4________3 // | / // | /2 // | \ // |0______\1 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(0, 0), new IntPoint(1000, 0), new IntPoint(900, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 2); } // ccw { // 2________1 // | / // | /0 // | \ // |3______\4 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(100, 0) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 0); } // ccw { // 2________1 // \ / // \3 /0 // / \ // /4_____\5 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 3); } // ccw { // 5________4 // \ / // \0 /3 // / \ // /1_____\2 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 0); } // find the right point wound cw (inside hole loops) { // 1________2 // | / // | /3 // | \ // |0______\4 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50), new IntPoint(100, 0) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 2); } // find the right point wound cw { // 2________3 // | / // | /4 // | \ // |1______\0 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(100, 0), new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 3); } // cw { // 4________5 // \ / // \3 /0 // / \ // /2_____\1 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(90, 50), new IntPoint(100, 0), new IntPoint(0, 0), new IntPoint(10, 50), new IntPoint(0, 100), new IntPoint(100, 100) }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 4); } // cw { // 1________2 // \ / // \0 /3 // / \ // /5_____\4 List <IntPoint> testPoints = new List <IntPoint> { new IntPoint(10, 50), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50), new IntPoint(100, 0), new IntPoint(0, 0), }; int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(testPoints); Assert.IsTrue(bestPoint == 1); } }
//Add a single layer from a single extruder to the GCode private void QueueAirGappedExtruderLayerToGCode(LayerDataStorage slicingData, GCodePlanner gcodeLayer, int extruderIndex, int layerIndex, int extrusionWidth_um, int fanSpeedPercent, long currentZ_um) { if (config.generateSupport && !config.continuousSpiralOuterPerimeter && layerIndex > 0) { int prevExtruder = gcodeLayer.getExtruder(); bool extruderChanged = gcodeLayer.SetExtruder(extruderIndex); SliceLayer layer = slicingData.Extruders[extruderIndex].Layers[layerIndex]; IslandOrderOptimizer partOrderOptimizer = new IslandOrderOptimizer(new IntPoint()); for (int partIndex = 0; partIndex < layer.Islands.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } partOrderOptimizer.AddPolygon(layer.Islands[partIndex].InsetToolPaths[0][0]); } partOrderOptimizer.Optimize(); List<Polygons> bottomFillIslandPolygons = new List<Polygons>(); for (int inlandIndex = 0; inlandIndex < partOrderOptimizer.bestIslandOrderIndex.Count; inlandIndex++) { if (config.continuousSpiralOuterPerimeter && inlandIndex > 0) { continue; } LayerIsland part = layer.Islands[partOrderOptimizer.bestIslandOrderIndex[inlandIndex]]; if (config.avoidCrossingPerimeters) { gcodeLayer.SetOuterPerimetersToAvoidCrossing(part.AvoidCrossingBoundery); } else { gcodeLayer.SetAlwaysRetract(true); } Polygons fillPolygons = new Polygons(); Polygons topFillPolygons = new Polygons(); Polygons bridgePolygons = new Polygons(); Polygons bottomFillPolygons = new Polygons(); CalculateInfillData(slicingData, extruderIndex, layerIndex, part, ref bottomFillPolygons, ref fillPolygons, ref topFillPolygons, ref bridgePolygons); bottomFillIslandPolygons.Add(bottomFillPolygons); // Write the bridge polygons out first so the perimeter will have more to hold to while bridging the gaps. // It would be even better to slow down the perimeters that are part of bridges but that is a bit harder. if (bridgePolygons.Count > 0) { gcode.WriteFanCommand(config.bridgeFanSpeedPercent); QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, bridgePolygons, airGappedBottomConfig, SupportWriteType.SupportedAreas); } if (config.numberOfPerimeters > 0) { if (inlandIndex != lastPartIndex) { // force a retract if changing islands gcodeLayer.ForceRetract(); lastPartIndex = inlandIndex; } if (config.continuousSpiralOuterPerimeter) { if (layerIndex >= config.numberOfBottomLayers) { inset0Config.spiralize = true; } } } //After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter. if (!config.continuousSpiralOuterPerimeter || layerIndex < config.numberOfBottomLayers) { gcodeLayer.MoveInsideTheOuterPerimeter(extrusionWidth_um * 2); } // Print everything but the first perimeter from the outside in so the little parts have more to stick to. for (int perimeterIndex = 1; perimeterIndex < part.InsetToolPaths.Count; perimeterIndex++) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, part.InsetToolPaths[perimeterIndex], airGappedBottomConfig, SupportWriteType.SupportedAreas); } // then 0 if (part.InsetToolPaths.Count > 0) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, part.InsetToolPaths[0], airGappedBottomConfig, SupportWriteType.SupportedAreas); } QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, bottomFillIslandPolygons[inlandIndex], airGappedBottomConfig, SupportWriteType.SupportedAreas); } } gcodeLayer.SetOuterPerimetersToAvoidCrossing(null); }
public void QueuePolygonsByOptimizer(Polygons polygons, GCodePathConfig config) { IslandOrderOptimizer orderOptimizer = new IslandOrderOptimizer(LastPosition); orderOptimizer.AddPolygons(polygons); orderOptimizer.Optimize(config); for (int i = 0; i < orderOptimizer.bestIslandOrderIndex.Count; i++) { int polygonIndex = orderOptimizer.bestIslandOrderIndex[i]; QueuePolygon(polygons[polygonIndex], orderOptimizer.startIndexInPolygon[polygonIndex], config); } }
//Add a single layer from a single extruder to the GCode private void QueueExtruderLayerToGCode(LayerDataStorage slicingData, GCodePlanner gcodeLayer, int extruderIndex, int layerIndex, int extrusionWidth_um, int fanSpeedPercent, long currentZ_um) { int prevExtruder = gcodeLayer.getExtruder(); bool extruderChanged = gcodeLayer.SetExtruder(extruderIndex); SliceLayer layer = slicingData.Extruders[extruderIndex].Layers[layerIndex]; if (extruderChanged) { addWipeTower(slicingData, gcodeLayer, layerIndex, prevExtruder, extrusionWidth_um); } if (slicingData.wipeShield.Count > 0 && slicingData.Extruders.Count > 1) { gcodeLayer.SetAlwaysRetract(true); gcodeLayer.QueuePolygonsByOptimizer(slicingData.wipeShield[layerIndex], skirtConfig); gcodeLayer.SetAlwaysRetract(!config.avoidCrossingPerimeters); } IslandOrderOptimizer islandOrderOptimizer = new IslandOrderOptimizer(new IntPoint()); for (int partIndex = 0; partIndex < layer.Islands.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } islandOrderOptimizer.AddPolygon(layer.Islands[partIndex].InsetToolPaths[0][0]); } islandOrderOptimizer.Optimize(); List<Polygons> bottomFillIslandPolygons = new List<Polygons>(); for (int islandOrderIndex = 0; islandOrderIndex < islandOrderOptimizer.bestIslandOrderIndex.Count; islandOrderIndex++) { if (config.continuousSpiralOuterPerimeter && islandOrderIndex > 0) { continue; } LayerIsland island = layer.Islands[islandOrderOptimizer.bestIslandOrderIndex[islandOrderIndex]]; if (config.avoidCrossingPerimeters) { gcodeLayer.SetOuterPerimetersToAvoidCrossing(island.AvoidCrossingBoundery); } else { gcodeLayer.SetAlwaysRetract(true); } Polygons fillPolygons = new Polygons(); Polygons topFillPolygons = new Polygons(); Polygons bridgePolygons = new Polygons(); Polygons bottomFillPolygons = new Polygons(); CalculateInfillData(slicingData, extruderIndex, layerIndex, island, ref bottomFillPolygons, ref fillPolygons, ref topFillPolygons, ref bridgePolygons); bottomFillIslandPolygons.Add(bottomFillPolygons); // Write the bridge polygons out first so the perimeter will have more to hold to while bridging the gaps. // It would be even better to slow down the perimeters that are part of bridges but that is a bit harder. if (bridgePolygons.Count > 0) { gcode.WriteFanCommand(config.bridgeFanSpeedPercent); QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, bridgePolygons, airGappedBottomConfig, SupportWriteType.UnsupportedAreas); } if (config.numberOfPerimeters > 0) { if (islandOrderIndex != lastPartIndex) { // force a retract if changing islands if (config.retractWhenChangingIslands) { gcodeLayer.ForceRetract(); } lastPartIndex = islandOrderIndex; } if (config.continuousSpiralOuterPerimeter) { if (layerIndex >= config.numberOfBottomLayers) { inset0Config.spiralize = true; } } // If we are on the very first layer we start with the outside so that we can stick to the bed better. if (config.outsidePerimetersFirst || layerIndex == 0 || inset0Config.spiralize) { if (inset0Config.spiralize) { if (island.InsetToolPaths.Count > 0) { Polygon outsideSinglePolygon = island.InsetToolPaths[0][0]; gcodeLayer.QueuePolygonsByOptimizer(new Polygons() { outsideSinglePolygon }, inset0Config); } } else { // First the outside (this helps with accuracy) if (island.InsetToolPaths.Count > 0) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, island.InsetToolPaths[0], inset0Config, SupportWriteType.UnsupportedAreas); } for (int perimeterIndex = 1; perimeterIndex < island.InsetToolPaths.Count; perimeterIndex++) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, island.InsetToolPaths[perimeterIndex], insetXConfig, SupportWriteType.UnsupportedAreas); } } } else // This is so we can do overhangs better (the outside can stick a bit to the inside). { // Figure out where the seam hiding start point is for inset 0 and move to that spot so // we have the minimum travel while starting inset 0 after printing the rest of the insets if (island?.InsetToolPaths?[0]?[0]?.Count > 0) { int bestPoint = IslandOrderOptimizer.GetBestEdgeIndex(island.InsetToolPaths[0][0]); gcodeLayer.QueueTravel(island.InsetToolPaths[0][0][bestPoint]); } // Print everything but the first perimeter from the outside in so the little parts have more to stick to. for (int perimeterIndex = 1; perimeterIndex < island.InsetToolPaths.Count; perimeterIndex++) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, island.InsetToolPaths[perimeterIndex], insetXConfig, SupportWriteType.UnsupportedAreas); } // then 0 if (island.InsetToolPaths.Count > 0) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, island.InsetToolPaths[0], inset0Config, SupportWriteType.UnsupportedAreas); } } } gcodeLayer.QueuePolygonsByOptimizer(fillPolygons, fillConfig); QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, bottomFillPolygons, bottomFillConfig, SupportWriteType.UnsupportedAreas); gcodeLayer.QueuePolygonsByOptimizer(topFillPolygons, topFillConfig); //After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter. if (!config.continuousSpiralOuterPerimeter || layerIndex < config.numberOfBottomLayers) { gcodeLayer.MoveInsideTheOuterPerimeter(extrusionWidth_um * 2); } } gcodeLayer.SetOuterPerimetersToAvoidCrossing(null); }