public void GenerateSkirt(int distance, int extrusionWidth_um, int numberOfLoops, int brimCount, int minLength, ConfigSettings config) { LayerDataStorage storage = this; bool externalOnly = (distance > 0); List <Polygons> skirtLoops = new List <Polygons>(); Polygons skirtPolygons = GetSkirtBounds(config, storage, externalOnly, distance, extrusionWidth_um, brimCount); if (skirtPolygons.Count > 0) { // Find convex hull for the skirt outline Polygons convexHull = new Polygons(new[] { skirtPolygons.CreateConvexHull() }); // Create skirt loops from the ConvexHull for (int skirtLoop = 0; skirtLoop < numberOfLoops; skirtLoop++) { int offsetDistance = distance + extrusionWidth_um * skirtLoop + extrusionWidth_um / 2; storage.skirt.AddAll(convexHull.Offset(offsetDistance)); int length = (int)storage.skirt.PolygonLength(); if (skirtLoop + 1 >= numberOfLoops && length > 0 && length < minLength) { // add more loops for as long as we have not extruded enough length numberOfLoops++; } } } }
public void GenerateRaftOutlines(long extraDistanceAroundPart_um, ConfigSettings config) { LayerDataStorage storage = this; for (int extruderIndex = 0; extruderIndex < storage.Extruders.Count; extruderIndex++) { if (config.ContinuousSpiralOuterPerimeter && extruderIndex > 0) { continue; } if (storage.Extruders[extruderIndex].Layers.Count < 1) { continue; } SliceLayer layer = storage.Extruders[extruderIndex].Layers[0]; // let's find the first layer that has something in it for the raft rather than a zero layer if (layer.Islands.Count == 0 && storage.Extruders[extruderIndex].Layers.Count > 2) { layer = storage.Extruders[extruderIndex].Layers[1]; } for (int partIndex = 0; partIndex < layer.Islands.Count; partIndex++) { if (config.ContinuousSpiralOuterPerimeter && partIndex > 0) { continue; } storage.raftOutline = storage.raftOutline.CreateUnion(layer.Islands[partIndex].IslandOutline.Offset(extraDistanceAroundPart_um)); } } storage.raftOutline = storage.raftOutline.CreateUnion(storage.WipeLayer(0).Offset(extraDistanceAroundPart_um)); if (storage.WipeShield.Count > 0 && storage.WipeShield[0].Count > 0) { storage.raftOutline = storage.raftOutline.CreateUnion(storage.WipeShield[0].Offset(extraDistanceAroundPart_um)); } if (storage.Support != null) { storage.raftOutline = storage.raftOutline.CreateUnion(storage.Support.GetBedOutlines().Offset(extraDistanceAroundPart_um)); } }
public void DumpLayerparts(string filename) { LayerDataStorage storage = this; StreamWriter streamToWriteTo = new StreamWriter(filename); streamToWriteTo.Write("<!DOCTYPE html><html><body>"); IntPoint modelSize = storage.modelSize; IntPoint modelMin = storage.modelMin; for (int extruderIndex = 0; extruderIndex < storage.Extruders.Count; extruderIndex++) { for (int layerNr = 0; layerNr < storage.Extruders[extruderIndex].Layers.Count; layerNr++) { streamToWriteTo.Write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n"); SliceLayer layer = storage.Extruders[extruderIndex].Layers[layerNr]; for (int i = 0; i < layer.Islands.Count; i++) { LayerIsland part = layer.Islands[i]; for (int j = 0; j < part.IslandOutline.Count; j++) { streamToWriteTo.Write("<polygon points=\""); for (int k = 0; k < part.IslandOutline[j].Count; k++) { streamToWriteTo.Write("{0},{1} ".FormatWith((float)(part.IslandOutline[j][k].X - modelMin.X) / modelSize.X * 500, (float)(part.IslandOutline[j][k].Y - modelMin.Y) / modelSize.Y * 500)); } if (j == 0) { streamToWriteTo.Write("\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n"); } else { streamToWriteTo.Write("\" style=\"fill:red; stroke:black;stroke-width:1\" />\n"); } } } streamToWriteTo.Write("</svg>\n"); } } streamToWriteTo.Write("</body></html>"); streamToWriteTo.Close(); }
private static Polygons GetSkirtBounds(ConfigSettings config, LayerDataStorage storage, bool externalOnly, int distance, int extrusionWidth_um, int brimCount) { bool hasWipeTower = storage.wipeTower.PolygonLength() > 0; Polygons skirtPolygons = hasWipeTower ? new Polygons(storage.wipeTower) : new Polygons(); if (config.EnableRaft) { skirtPolygons = skirtPolygons.CreateUnion(storage.raftOutline); } else { Polygons allOutlines = new Polygons(); // Loop over every extruder for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { // Only process the first extruder on spiral vase or // skip extruders that have empty layers if (config.ContinuousSpiralOuterPerimeter) { SliceLayer layer0 = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer0.Islands[0]?.IslandOutline); break; } // Add the layers outline to allOutlines SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer.AllOutlines); } if (brimCount > 0) { Polygons brimLoops = new Polygons(); // Loop over the requested brimCount creating and unioning a new perimeter for each island for (int brimIndex = 0; brimIndex < brimCount; brimIndex++) { int offsetDistance = extrusionWidth_um * brimIndex + extrusionWidth_um / 2; Polygons unionedIslandOutlines = new Polygons(); // Grow each island by the current brim distance foreach (var island in allOutlines) { var polygons = new Polygons(); polygons.Add(island); // Union the island brims unionedIslandOutlines = unionedIslandOutlines.CreateUnion(polygons.Offset(offsetDistance)); } // Extend the polygons to account for the brim (ensures convex hull takes this data into account) brimLoops.AddAll(unionedIslandOutlines); } // TODO: This is a quick hack, reuse the skirt data to stuff in the brim. Good enough from proof of concept storage.skirt.AddAll(brimLoops); skirtPolygons = skirtPolygons.CreateUnion(brimLoops); } else { skirtPolygons = skirtPolygons.CreateUnion(allOutlines); } if (storage.support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.support.GetBedOutlines()); } } return skirtPolygons; }
private void CalculateInfillData(LayerDataStorage slicingData, int extruderIndex, int layerIndex, LayerIsland part, ref Polygons bottomFillPolygons, ref Polygons fillPolygons, ref Polygons topFillPolygons, ref Polygons bridgePolygons) { // generate infill the bottom layer including bridging foreach (Polygons outline in part.SolidBottomToolPaths.ProcessIntoSeparatIslands()) { if (layerIndex > 0) { double bridgeAngle = 0; SliceLayer previousLayer = slicingData.Extruders[extruderIndex].Layers[layerIndex - 1]; if (!config.generateSupport && previousLayer.BridgeAngle(outline, out bridgeAngle)) { Infill.GenerateLinePaths(outline, ref bridgePolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, bridgeAngle); } else { Infill.GenerateLinePaths(outline, ref bottomFillPolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } } else { Infill.GenerateLinePaths(outline, ref bottomFillPolygons, config.firstLayerExtrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } } // generate infill for the top layer foreach (Polygons outline in part.SolidTopToolPaths.ProcessIntoSeparatIslands()) { Infill.GenerateLinePaths(outline, ref topFillPolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } // generate infill intermediate layers foreach (Polygons outline in part.SolidInfillToolPaths.ProcessIntoSeparatIslands()) { if (true) // use the old infill method { Infill.GenerateLinePaths(outline, ref fillPolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle + 90 * (layerIndex % 2)); } else // use the new concentric infill (not tested enough yet) have to handle some bad cases better { double oldInfillPercent = config.infillPercent; config.infillPercent = 100; Infill.GenerateConcentricInfill(config, outline, ref fillPolygons); config.infillPercent = oldInfillPercent; } } double fillAngle = config.infillStartingAngle; // generate the sparse infill for this part on this layer if (config.infillPercent > 0) { switch (config.infillType) { case ConfigConstants.INFILL_TYPE.LINES: if ((layerIndex & 1) == 1) { fillAngle += 90; } Infill.GenerateLineInfill(config, part.InfillToolPaths, ref fillPolygons, fillAngle); break; case ConfigConstants.INFILL_TYPE.GRID: Infill.GenerateGridInfill(config, part.InfillToolPaths, ref fillPolygons, fillAngle); break; case ConfigConstants.INFILL_TYPE.TRIANGLES: Infill.GenerateTriangleInfill(config, part.InfillToolPaths, ref fillPolygons, fillAngle); break; case ConfigConstants.INFILL_TYPE.HEXAGON: Infill.GenerateHexagonInfill(config, part.InfillToolPaths, ref fillPolygons, fillAngle, layerIndex); break; case ConfigConstants.INFILL_TYPE.CONCENTRIC: Infill.GenerateConcentricInfill(config, part.InfillToolPaths, ref fillPolygons); break; default: throw new NotImplementedException(); } } }
//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]; PathOrderOptimizer partOrderOptimizer = new PathOrderOptimizer(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.bestPolygonOrderIndex.Count; inlandIndex++) { if (config.continuousSpiralOuterPerimeter && inlandIndex > 0) { continue; } LayerIsland part = layer.Islands[partOrderOptimizer.bestPolygonOrderIndex[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); gcodeLayer.QueuePolygonsByOptimizer(bridgePolygons, bridgConfig); } 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], insetXConfig, SupportWriteType.SupportedAreas); } // then 0 if (part.InsetToolPaths.Count > 0) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, part.InsetToolPaths[0], inset0Config, SupportWriteType.SupportedAreas); } QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, bottomFillIslandPolygons[inlandIndex], bottomFillConfig, SupportWriteType.SupportedAreas); } } gcodeLayer.SetOuterPerimetersToAvoidCrossing(null); }
//Add a single layer from a single extruder to the GCode private void QueueExtruderLayerToGCode(LayerDataStorage slicingData, GCodePlanner layerGcodePlanner, int extruderIndex, int layerIndex, int extrusionWidth_um, long currentZ_um) { if(extruderIndex > slicingData.Extruders.Count-1) { return; } SliceLayer layer = slicingData.Extruders[extruderIndex].Layers[layerIndex]; if(layer.AllOutlines.Count == 0 && config.WipeShieldDistanceFromObject == 0) { // don't do anything on this layer return; } if (slicingData.wipeShield.Count > 0 && slicingData.Extruders.Count > 1) { layerGcodePlanner.SetAlwaysRetract(true); layerGcodePlanner.QueuePolygonsByOptimizer(slicingData.wipeShield[layerIndex], skirtConfig); layerGcodePlanner.SetAlwaysRetract(!config.AvoidCrossingPerimeters); } PathOrderOptimizer islandOrderOptimizer = new PathOrderOptimizer(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) { layerGcodePlanner.SetOuterPerimetersToAvoidCrossing(island.AvoidCrossingBoundary); } else { layerGcodePlanner.SetAlwaysRetract(true); } Polygons fillPolygons = new Polygons(); Polygons topFillPolygons = new Polygons(); Polygons bridgePolygons = new Polygons(); Polygons bottomFillPolygons = new Polygons(); CalculateInfillData(slicingData, extruderIndex, layerIndex, island, bottomFillPolygons, fillPolygons, topFillPolygons, 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) { QueuePolygonsConsideringSupport(layerIndex, layerGcodePlanner, bridgePolygons, bridgeConfig, SupportWriteType.UnsupportedAreas); } if (config.NumberOfPerimeters > 0) { if (islandOrderIndex != lastPartIndex) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { layerGcodePlanner.ForceRetract(); } lastPartIndex = islandOrderIndex; } if (config.ContinuousSpiralOuterPerimeter) { if (layerIndex >= config.NumberOfBottomLayers) { inset0Config.spiralize = true; } } // 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 && !config.ContinuousSpiralOuterPerimeter) { int bestPoint = PathOrderOptimizer.GetBestIndex(island.InsetToolPaths[0][0], config.ExtrusionWidth_um); layerGcodePlanner.QueueTravel(island.InsetToolPaths[0][0][bestPoint]); } // Put all the insets into a new list so we can keep track of what has been printed. List<Polygons> insetsToPrint = new List<Polygons>(island.InsetToolPaths.Count); for (int insetIndex = 0; insetIndex < island.InsetToolPaths.Count; insetIndex++) { insetsToPrint.Add(new Polygons()); for (int polygonIndex = 0; polygonIndex < island.InsetToolPaths[insetIndex].Count; polygonIndex++) { if (island.InsetToolPaths[insetIndex][polygonIndex].Count > 0) { insetsToPrint[insetIndex].Add(island.InsetToolPaths[insetIndex][polygonIndex]); } } } // 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]; layerGcodePlanner.QueuePolygonsByOptimizer(new Polygons() { outsideSinglePolygon }, inset0Config); } } else { int insetCount = CountInsetsToPrint(insetsToPrint); while (insetCount > 0) { bool limitDistance = false; if (island.InsetToolPaths.Count > 0) { QueueClosetsInset(insetsToPrint[0], limitDistance, inset0Config, layerIndex, layerGcodePlanner); } if (island.InsetToolPaths.Count > 1) { // Move to the closest inset 1 and print it limitDistance = QueueClosetsInset(insetsToPrint[1], limitDistance, insetXConfig, layerIndex, layerGcodePlanner); for (int insetIndex = 2; insetIndex < island.InsetToolPaths.Count; insetIndex++) { limitDistance = QueueClosetsInset( insetsToPrint[insetIndex], limitDistance, insetIndex == 0 ? inset0Config : insetXConfig, layerIndex, layerGcodePlanner); } } insetCount = CountInsetsToPrint(insetsToPrint); } } } else // This is so we can do overhangs better (the outside can stick a bit to the inside). { int insetCount = CountInsetsToPrint(insetsToPrint); while (insetCount > 0) { bool limitDistance = false; if (island.InsetToolPaths.Count > 0) { // Move to the closest inset 1 and print it for (int insetIndex = island.InsetToolPaths.Count-1; insetIndex >= 0; insetIndex--) { limitDistance = QueueClosetsInset( insetsToPrint[insetIndex], limitDistance, insetIndex == 0 ? inset0Config : insetXConfig, layerIndex, layerGcodePlanner); } } insetCount = CountInsetsToPrint(insetsToPrint); } } } // TODO: Put all of these segments into a list that can be queued together and still preserver their individual config settings. // This will make the total amount of travel while printing infill much less. layerGcodePlanner.QueuePolygonsByOptimizer(fillPolygons, fillConfig); QueuePolygonsConsideringSupport(layerIndex, layerGcodePlanner, bottomFillPolygons, bottomFillConfig, SupportWriteType.UnsupportedAreas); layerGcodePlanner.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) { layerGcodePlanner.MoveInsideTheOuterPerimeter(extrusionWidth_um * 2); } } // Find the thin lines for this layer and add them to the queue if (false) // this code is just for test. LBB { Polygons fillPolygons = new Polygons(); foreach (var island in layer.Islands) { List<Point3> path = new List<Point3>(); List<PathAndWidth> thinLines; foreach (var outline in island.IslandOutline.Offset(-extrusionWidth_um * 0)) { foreach (var point in outline) { path.Add(new Point3(point, currentZ_um)); } } if (layerGcodePlanner.FindThinLines(path, extrusionWidth_um - 2, out thinLines)) { foreach (var widthPath in thinLines) { Polygon thinPath = new Polygon(); foreach (var point in widthPath.Path) { thinPath.Add(new IntPoint(point.x, point.y)); } fillPolygons.Add(thinPath); } } } layerGcodePlanner.QueuePolygonsByOptimizer(fillPolygons, fillConfig); } layerGcodePlanner.SetOuterPerimetersToAvoidCrossing(null); }
public void WriteRaftGCodeIfRequired(GCodeExport gcode, ConfigSettings config) { LayerDataStorage storage = this; if (config.ShouldGenerateRaft()) { GCodePathConfig raftBaseConfig = new GCodePathConfig("raftBaseConfig"); raftBaseConfig.SetData(config.FirstLayerSpeed, config.RaftBaseExtrusionWidth_um, "SUPPORT"); GCodePathConfig raftMiddleConfig = new GCodePathConfig("raftMiddleConfig"); raftMiddleConfig.SetData(config.RaftPrintSpeed, config.RaftInterfaceExtrusionWidth_um, "SUPPORT"); GCodePathConfig raftSurfaceConfig = new GCodePathConfig("raftMiddleConfig"); raftSurfaceConfig.SetData((config.RaftSurfacePrintSpeed > 0) ? config.RaftSurfacePrintSpeed : config.RaftPrintSpeed, config.RaftSurfaceExtrusionWidth_um, "SUPPORT"); // create the raft base { gcode.WriteComment("RAFT BASE"); LayerGCodePlanner layerPlanner = new LayerGCodePlanner(config, gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); if (config.RaftExtruder >= 0) { // if we have a specified raft extruder use it layerPlanner.SetExtruder(config.RaftExtruder); } else if (config.SupportExtruder >= 0) { // else preserve the old behavior of using the support extruder if set. layerPlanner.SetExtruder(config.SupportExtruder); } gcode.CurrentZ = config.RaftBaseThickness_um; gcode.LayerChanged(-3, config.RaftBaseThickness_um); gcode.SetExtrusion(config.RaftBaseThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); // write the skirt around the raft layerPlanner.QueuePolygonsByOptimizer(storage.Skirt, null, raftBaseConfig, 0); List <Polygons> raftIslands = storage.raftOutline.ProcessIntoSeparateIslands(); foreach (var raftIsland in raftIslands) { // write the outline of the raft layerPlanner.QueuePolygonsByOptimizer(raftIsland, null, raftBaseConfig, 0); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(raftIsland.Offset(-config.RaftBaseExtrusionWidth_um), raftLines, config.RaftBaseLineSpacing_um, config.InfillExtendIntoPerimeter_um, 0); // write the inside of the raft base layerPlanner.QueuePolygonsByOptimizer(raftLines, null, raftBaseConfig, 0); if (config.RetractWhenChangingIslands) { layerPlanner.ForceRetract(); } } layerPlanner.WriteQueuedGCode(config.RaftBaseThickness_um); } // raft middle layers { gcode.WriteComment("RAFT MIDDLE"); LayerGCodePlanner layerPlanner = new LayerGCodePlanner(config, gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); gcode.CurrentZ = config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um; gcode.LayerChanged(-2, config.RaftInterfaceThicknes_um); gcode.SetExtrusion(config.RaftInterfaceThicknes_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftInterfaceLineSpacing_um, config.InfillExtendIntoPerimeter_um, 45); layerPlanner.QueuePolygonsByOptimizer(raftLines, null, raftMiddleConfig, 0); layerPlanner.WriteQueuedGCode(config.RaftInterfaceThicknes_um); } for (int raftSurfaceIndex = 1; raftSurfaceIndex <= config.RaftSurfaceLayers; raftSurfaceIndex++) { gcode.WriteComment("RAFT SURFACE"); LayerGCodePlanner layerPlanner = new LayerGCodePlanner(config, gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); gcode.CurrentZ = config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um + config.RaftSurfaceThickness_um * raftSurfaceIndex; gcode.LayerChanged(-1, config.RaftSurfaceThickness_um); gcode.SetExtrusion(config.RaftSurfaceThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); Polygons raftLines = new Polygons(); if (raftSurfaceIndex == config.RaftSurfaceLayers) { // make sure the top layer of the raft is 90 degrees offset to the first layer of the part so that it has minimum contact points. Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftSurfaceLineSpacing_um, config.InfillExtendIntoPerimeter_um, config.InfillStartingAngle + 90); } else { Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftSurfaceLineSpacing_um, config.InfillExtendIntoPerimeter_um, 90 * raftSurfaceIndex); } layerPlanner.QueuePolygonsByOptimizer(raftLines, null, raftSurfaceConfig, 0); layerPlanner.WriteQueuedGCode(config.RaftInterfaceThicknes_um); } } }
private void writeGCode(LayerDataStorage slicingData) { gcode.WriteComment("filamentDiameter = {0}".FormatWith(config.filamentDiameter)); gcode.WriteComment("extrusionWidth = {0}".FormatWith(config.extrusionWidth)); gcode.WriteComment("firstLayerExtrusionWidth = {0}".FormatWith(config.firstLayerExtrusionWidth)); gcode.WriteComment("layerThickness = {0}".FormatWith(config.layerThickness)); gcode.WriteComment("firstLayerThickness = {0}".FormatWith(config.firstLayerThickness)); if (fileNumber == 1) { if (gcode.GetOutputType() == ConfigConstants.OUTPUT_TYPE.ULTIGCODE) { gcode.WriteComment("TYPE:UltiGCode"); gcode.WriteComment("TIME:<__TIME__>"); gcode.WriteComment("MATERIAL:<FILAMENT>"); gcode.WriteComment("MATERIAL2:<FILAMEN2>"); } gcode.WriteCode(config.startCode); } else { gcode.WriteFanCommand(0); gcode.ResetExtrusionValue(); gcode.WriteRetraction(); gcode.setZ(maxObjectHeight + 5000); gcode.WriteMove(gcode.GetPosition(), config.travelSpeed, 0); gcode.WriteMove(new Point3(slicingData.modelMin.x, slicingData.modelMin.y, gcode.CurrentZ), config.travelSpeed, 0); } fileNumber++; int totalLayers = slicingData.Extruders[0].Layers.Count; // let's remove any of the layers on top that are empty { for (int layerIndex = totalLayers - 1; layerIndex >= 0; layerIndex--) { bool layerHasData = false; foreach (ExtruderLayers currentExtruder in slicingData.Extruders) { SliceLayer currentLayer = currentExtruder.Layers[layerIndex]; for (int partIndex = 0; partIndex < currentExtruder.Layers[layerIndex].Islands.Count; partIndex++) { LayerIsland currentIsland = currentLayer.Islands[partIndex]; if (currentIsland.IslandOutline.Count > 0) { layerHasData = true; break; } } } if (layerHasData) { break; } totalLayers--; } } gcode.WriteComment("Layer count: {0}".FormatWith(totalLayers)); // keep the raft generation code inside of raft slicingData.WriteRaftGCodeIfRequired(config, gcode); for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { if (MatterSlice.Canceled) { return; } LogOutput.Log("Writing Layers {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers)); LogOutput.logProgress("export", layerIndex + 1, totalLayers); if (layerIndex == 0) { skirtConfig.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "SKIRT"); inset0Config.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "WALL-OUTER"); insetXConfig.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "WALL-INNER"); fillConfig.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "FILL", false); topFillConfig.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "TOP-FILL", false); bottomFillConfig.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "BOTTOM-FILL", false); bridgConfig.SetData(config.firstLayerSpeed, config.firstLayerExtrusionWidth_um, "BRIDGE"); supportNormalConfig.SetData(config.firstLayerSpeed, config.supportExtrusionWidth_um, "SUPPORT"); supportInterfaceConfig.SetData(config.firstLayerSpeed, config.extrusionWidth_um, "SUPPORT-INTERFACE"); } else { skirtConfig.SetData(config.insidePerimetersSpeed, config.extrusionWidth_um, "SKIRT"); inset0Config.SetData(config.outsidePerimeterSpeed, config.outsideExtrusionWidth_um, "WALL-OUTER"); insetXConfig.SetData(config.insidePerimetersSpeed, config.extrusionWidth_um, "WALL-INNER"); fillConfig.SetData(config.infillSpeed, config.extrusionWidth_um, "FILL", false); topFillConfig.SetData(config.topInfillSpeed, config.firstLayerExtrusionWidth_um, "TOP-FILL", false); bottomFillConfig.SetData(config.infillSpeed, config.firstLayerExtrusionWidth_um, "BOTTOM-FILL", false); bridgConfig.SetData(config.bridgeSpeed, config.extrusionWidth_um, "BRIDGE"); supportNormalConfig.SetData(config.supportMaterialSpeed, config.supportExtrusionWidth_um, "SUPPORT"); supportInterfaceConfig.SetData(config.supportMaterialSpeed - 5, config.extrusionWidth_um, "SUPPORT-INTERFACE"); } gcode.WriteComment("LAYER:{0}".FormatWith(layerIndex)); if (layerIndex == 0) { gcode.SetExtrusion(config.firstLayerThickness_um, config.filamentDiameter_um, config.extrusionMultiplier); } else { gcode.SetExtrusion(config.layerThickness_um, config.filamentDiameter_um, config.extrusionMultiplier); } GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.travelSpeed, config.minimumTravelToCauseRetraction_um); // get the correct height for this layer int z = config.firstLayerThickness_um + layerIndex * config.layerThickness_um; if (config.enableRaft) { z += config.raftBaseThickness_um + config.raftInterfaceThicknes_um + config.raftSurfaceLayers * config.raftSurfaceThickness_um; if (layerIndex == 0) { // We only raise the first layer of the print up by the air gap. // To give it: // Less press into the raft // More time to cool // more surface area to air while extruding z += config.raftAirGap_um; } } gcode.setZ(z); // We only create the skirt if we are on layer 0. if (layerIndex == 0 && !config.ShouldGenerateRaft()) { QueueSkirtToGCode(slicingData, gcodeLayer, layerIndex); } if (slicingData.support != null) { slicingData.support.QueueNormalSupportLayer(config, gcodeLayer, layerIndex, supportNormalConfig, supportInterfaceConfig); } int fanSpeedPercent = GetFanSpeed(layerIndex, gcodeLayer); for (int extruderIndex = 0; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { if (layerIndex == 0) { QueueExtruderLayerToGCode(slicingData, gcodeLayer, extruderIndex, layerIndex, config.firstLayerExtrusionWidth_um, fanSpeedPercent, z); } else { QueueExtruderLayerToGCode(slicingData, gcodeLayer, extruderIndex, layerIndex, config.extrusionWidth_um, fanSpeedPercent, z); } } if (slicingData.support != null) { z += config.supportAirGap_um; gcode.setZ(z); for (int extruderIndex = 0; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { QueueAirGappedExtruderLayerToGCode(slicingData, gcodeLayer, extruderIndex, layerIndex, config.extrusionWidth_um, fanSpeedPercent, z); } slicingData.support.QueueAirGappedBottomLayer(config, gcodeLayer, layerIndex, supportNormalConfig); } //Finish the layer by applying speed corrections for minimum layer times. gcodeLayer.ForceMinimumLayerTime(config.minimumLayerTimeSeconds, config.minimumPrintingSpeed); gcode.WriteFanCommand(fanSpeedPercent); int currentLayerThickness_um = config.layerThickness_um; if (layerIndex <= 0) { currentLayerThickness_um = config.firstLayerThickness_um; } gcodeLayer.WriteQueuedGCode(currentLayerThickness_um); } LogOutput.Log("Wrote layers in {0:0.00}s.\n".FormatWith(timeKeeper.Elapsed.TotalSeconds)); timeKeeper.Restart(); gcode.TellFileSize(); gcode.WriteFanCommand(0); //Store the object height for when we are printing multiple objects, as we need to clear every one of them when moving to the next position. maxObjectHeight = Math.Max(maxObjectHeight, slicingData.modelSize.z); }
private void CreateWipeShields(LayerDataStorage slicingData, int totalLayers) { for (int layerNr = 0; layerNr < totalLayers; layerNr++) { Polygons wipeShield = new Polygons(); for (int extruderIndex = 0; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { for (int partNr = 0; partNr < slicingData.Extruders[extruderIndex].Layers[layerNr].Islands.Count; partNr++) { wipeShield = wipeShield.CreateUnion(slicingData.Extruders[extruderIndex].Layers[layerNr].Islands[partNr].IslandOutline.Offset(config.wipeShieldDistanceFromShapes_um)); } } slicingData.wipeShield.Add(wipeShield); } for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { slicingData.wipeShield[layerIndex] = slicingData.wipeShield[layerIndex].Offset(-1000).Offset(1000); } int offsetAngle = (int)Math.Tan(60.0 * Math.PI / 180) * config.layerThickness_um;//Allow for a 60deg angle in the wipeShield. for (int layerNr = 1; layerNr < totalLayers; layerNr++) { slicingData.wipeShield[layerNr] = slicingData.wipeShield[layerNr].CreateUnion(slicingData.wipeShield[layerNr - 1].Offset(-offsetAngle)); } for (int layerNr = totalLayers - 1; layerNr > 0; layerNr--) { slicingData.wipeShield[layerNr - 1] = slicingData.wipeShield[layerNr - 1].CreateUnion(slicingData.wipeShield[layerNr].Offset(-offsetAngle)); } }
private void ProcessSliceData(LayerDataStorage slicingData) { if (config.continuousSpiralOuterPerimeter) { config.numberOfTopLayers = 0; config.infillPercent = 0; } MultiExtruders.ProcessBooleans(slicingData.Extruders, config.BooleanOpperations); MultiExtruders.RemoveExtruderIntersections(slicingData.Extruders); MultiExtruders.OverlapMultipleExtrudersSlightly(slicingData.Extruders, config.multiExtruderOverlapPercent); #if False LayerPart.dumpLayerparts(slicingData, "output.html"); #endif LogOutput.Log("Generating support map...\n"); if (config.generateSupport && !config.continuousSpiralOuterPerimeter) { slicingData.support = new NewSupport(config, slicingData.Extruders, 1); } slicingData.CreateIslandData(); int totalLayers = slicingData.Extruders[0].Layers.Count; #if DEBUG for (int extruderIndex = 1; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { if (totalLayers != slicingData.Extruders[extruderIndex].Layers.Count) { throw new Exception("All the extruders must have the same number of layers (they just can have empty layers)."); } } #endif for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { for (int extruderIndex = 0; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { if (MatterSlice.Canceled) { return; } int insetCount = config.numberOfPerimeters; if (config.continuousSpiralOuterPerimeter && (int)(layerIndex) < config.numberOfBottomLayers && layerIndex % 2 == 1) { //Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight. insetCount += 1; } SliceLayer layer = slicingData.Extruders[extruderIndex].Layers[layerIndex]; if (layerIndex == 0) { layer.GenerateInsets(config.firstLayerExtrusionWidth_um, config.firstLayerExtrusionWidth_um, insetCount); } else { layer.GenerateInsets(config.extrusionWidth_um, config.outsideExtrusionWidth_um, insetCount); } } LogOutput.Log("Creating Insets {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers)); } if (config.wipeShieldDistanceFromShapes_um > 0) { CreateWipeShields(slicingData, totalLayers); } LogOutput.Log("Generated inset in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.TotalSeconds)); timeKeeper.Restart(); for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { if (MatterSlice.Canceled) { return; } //Only generate bottom and top layers and infill for the first X layers when spiralize is chosen. if (!config.continuousSpiralOuterPerimeter || (int)(layerIndex) < config.numberOfBottomLayers) { for (int extruderIndex = 0; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { if (layerIndex == 0) { slicingData.Extruders[extruderIndex].GenerateTopAndBottoms(layerIndex, config.firstLayerExtrusionWidth_um, config.firstLayerExtrusionWidth_um, config.numberOfBottomLayers, config.numberOfTopLayers); } else { slicingData.Extruders[extruderIndex].GenerateTopAndBottoms(layerIndex, config.extrusionWidth_um, config.outsideExtrusionWidth_um, config.numberOfBottomLayers, config.numberOfTopLayers); } } } LogOutput.Log("Creating Top & Bottom Layers {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers)); } LogOutput.Log("Generated top bottom layers in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.TotalSeconds)); timeKeeper.Restart(); if (config.wipeTowerSize_um > 0) { Polygon p = new Polygon(); slicingData.wipeTower.Add(p); p.Add(new IntPoint(slicingData.modelMin.x - 3000, slicingData.modelMax.y + 3000)); p.Add(new IntPoint(slicingData.modelMin.x - 3000, slicingData.modelMax.y + 3000 + config.wipeTowerSize_um)); p.Add(new IntPoint(slicingData.modelMin.x - 3000 - config.wipeTowerSize_um, slicingData.modelMax.y + 3000 + config.wipeTowerSize_um)); p.Add(new IntPoint(slicingData.modelMin.x - 3000 - config.wipeTowerSize_um, slicingData.modelMax.y + 3000)); slicingData.wipePoint = new IntPoint(slicingData.modelMin.x - 3000 - config.wipeTowerSize_um / 2, slicingData.modelMax.y + 3000 + config.wipeTowerSize_um / 2); } if (config.enableRaft) { slicingData.GenerateRaftOutlines(config.raftExtraDistanceAroundPart_um, config); slicingData.GenerateSkirt( config.skirtDistance_um + config.raftBaseLineSpacing_um, config.raftBaseLineSpacing_um, config.numberOfSkirtLoops, config.skirtMinLength_um, config.raftBaseThickness_um, config); } else { slicingData.GenerateSkirt( config.skirtDistance_um, config.firstLayerExtrusionWidth_um, config.numberOfSkirtLoops, config.skirtMinLength_um, config.firstLayerThickness_um, config); } }
private void SliceModels(LayerDataStorage slicingData) { timeKeeper.Restart(); #if false optomizedModel.saveDebugSTL("debug_output.stl"); #endif LogOutput.Log("Slicing model...\n"); List<Slicer> slicerList = new List<Slicer>(); for (int optimizedMeshIndex = 0; optimizedMeshIndex < optomizedMeshCollection.OptimizedMeshes.Count; optimizedMeshIndex++) { Slicer slicer = new Slicer(optomizedMeshCollection.OptimizedMeshes[optimizedMeshIndex], config); slicerList.Add(slicer); } #if false slicerList[0].DumpSegmentsToGcode("Volume 0 Segments.gcode"); slicerList[0].DumpPolygonsToGcode("Volume 0 Polygons.gcode"); //slicerList[0].DumpPolygonsToHTML("Volume 0 Polygons.html"); #endif LogOutput.Log("Sliced model in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.TotalSeconds)); timeKeeper.Restart(); slicingData.modelSize = optomizedMeshCollection.size_um; slicingData.modelMin = optomizedMeshCollection.minXYZ_um; slicingData.modelMax = optomizedMeshCollection.maxXYZ_um; LogOutput.Log("Generating layer parts...\n"); for (int extruderIndex = 0; extruderIndex < slicerList.Count; extruderIndex++) { slicingData.Extruders.Add(new ExtruderLayers()); slicingData.Extruders[extruderIndex].InitializeLayerData(slicerList[extruderIndex]); if (config.enableRaft) { //Add the raft offset to each layer. for (int layerIndex = 0; layerIndex < slicingData.Extruders[extruderIndex].Layers.Count; layerIndex++) { slicingData.Extruders[extruderIndex].Layers[layerIndex].LayerZ += config.raftBaseThickness_um + config.raftInterfaceThicknes_um; } } } LogOutput.Log("Generated layer parts in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.TotalSeconds)); timeKeeper.Restart(); }
private void addWipeTower(LayerDataStorage slicingData, GCodePlanner gcodeLayer, int layerNr, int prevExtruder, int extrusionWidth_um) { if (config.wipeTowerSize_um < 1) { return; } //If we changed extruder, print the wipe/prime tower for this nozzle; gcodeLayer.QueuePolygonsByOptimizer(slicingData.wipeTower, supportInterfaceConfig); Polygons fillPolygons = new Polygons(); Infill.GenerateLinePaths(slicingData.wipeTower, ref fillPolygons, extrusionWidth_um, config.infillExtendIntoPerimeter_um, 45 + 90 * (layerNr % 2)); gcodeLayer.QueuePolygonsByOptimizer(fillPolygons, supportInterfaceConfig); //Make sure we wipe the old extruder on the wipe tower. gcodeLayer.QueueTravel(slicingData.wipePoint - config.extruderOffsets[prevExtruder] + config.extruderOffsets[gcodeLayer.getExtruder()]); }
public void WriteRaftGCodeIfRequired(ConfigSettings config, GCodeExport gcode) { LayerDataStorage storage = this; if (config.ShouldGenerateRaft()) { GCodePathConfig raftBaseConfig = new GCodePathConfig(config.FirstLayerSpeed, config.RaftBaseExtrusionWidth_um, "SUPPORT"); GCodePathConfig raftMiddleConfig = new GCodePathConfig(config.RaftPrintSpeed, config.RaftInterfaceExtrusionWidth_um, "SUPPORT"); GCodePathConfig raftSurfaceConfig = new GCodePathConfig((config.RaftSurfacePrintSpeed > 0) ? config.RaftSurfacePrintSpeed : config.RaftPrintSpeed, config.RaftSurfaceExtrusionWidth_um, "SUPPORT"); // create the raft base { gcode.WriteComment("LAYER:-3"); gcode.WriteComment("RAFT BASE"); GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um); if (config.RaftExtruder >= 0) { // if we have a specified raft extruder use it gcodeLayer.SetExtruder(config.RaftExtruder); } else if (config.SupportExtruder >= 0) { // else preserve the old behavior of using the support extruder if set. gcodeLayer.SetExtruder(config.SupportExtruder); } gcode.setZ(config.RaftBaseThickness_um); gcode.SetExtrusion(config.RaftBaseThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftBaseLineSpacing_um, config.InfillExtendIntoPerimeter_um, 0); // write the skirt around the raft gcodeLayer.QueuePolygonsByOptimizer(storage.skirt, raftBaseConfig); // write the outline of the raft gcodeLayer.QueuePolygonsByOptimizer(storage.raftOutline, raftBaseConfig); // write the inside of the raft base gcodeLayer.QueuePolygonsByOptimizer(raftLines, raftBaseConfig); gcodeLayer.WriteQueuedGCode(config.RaftBaseThickness_um); } if (config.RaftFanSpeedPercent > 0) { gcode.WriteFanCommand(config.RaftFanSpeedPercent); } // raft middle layers { gcode.WriteComment("LAYER:-2"); gcode.WriteComment("RAFT MIDDLE"); GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um); gcode.setZ(config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um); gcode.SetExtrusion(config.RaftInterfaceThicknes_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftInterfaceLineSpacing_um, config.InfillExtendIntoPerimeter_um, 45); gcodeLayer.QueuePolygonsByOptimizer(raftLines, raftMiddleConfig); gcodeLayer.WriteQueuedGCode(config.RaftInterfaceThicknes_um); } for (int raftSurfaceIndex = 1; raftSurfaceIndex <= config.RaftSurfaceLayers; raftSurfaceIndex++) { gcode.WriteComment("LAYER:-1"); gcode.WriteComment("RAFT SURFACE"); GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um); gcode.setZ(config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um + config.RaftSurfaceThickness_um * raftSurfaceIndex); gcode.SetExtrusion(config.RaftSurfaceThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); Polygons raftLines = new Polygons(); if (raftSurfaceIndex == config.RaftSurfaceLayers) { // make sure the top layer of the raft is 90 degrees offset to the first layer of the part so that it has minimum contact points. Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftSurfaceLineSpacing_um, config.InfillExtendIntoPerimeter_um, config.InfillStartingAngle + 90); } else { Infill.GenerateLinePaths(storage.raftOutline, raftLines, config.RaftSurfaceLineSpacing_um, config.InfillExtendIntoPerimeter_um, 90 * raftSurfaceIndex); } gcodeLayer.QueuePolygonsByOptimizer(raftLines, raftSurfaceConfig); gcodeLayer.WriteQueuedGCode(config.RaftInterfaceThicknes_um); } } }
private void writeGCode(LayerDataStorage slicingData) { gcode.WriteComment("filamentDiameter = {0}".FormatWith(config.FilamentDiameter)); gcode.WriteComment("extrusionWidth = {0}".FormatWith(config.ExtrusionWidth)); gcode.WriteComment("firstLayerExtrusionWidth = {0}".FormatWith(config.FirstLayerExtrusionWidth)); gcode.WriteComment("layerThickness = {0}".FormatWith(config.LayerThickness)); gcode.WriteComment("firstLayerThickness = {0}".FormatWith(config.FirstLayerThickness)); if (fileNumber == 1) { gcode.WriteCode(config.StartCode); } else { gcode.WriteFanCommand(0); gcode.ResetExtrusionValue(); gcode.WriteRetraction(); gcode.setZ(maxObjectHeight + 5000); gcode.WriteMove(gcode.GetPosition(), config.TravelSpeed, 0); gcode.WriteMove(new Point3(slicingData.modelMin.x, slicingData.modelMin.y, gcode.CurrentZ), config.TravelSpeed, 0); } fileNumber++; int totalLayers = slicingData.Extruders[0].Layers.Count; if (config.outputOnlyFirstLayer) { totalLayers = 1; } // let's remove any of the layers on top that are empty { for (int layerIndex = totalLayers - 1; layerIndex >= 0; layerIndex--) { bool layerHasData = false; foreach (ExtruderLayers currentExtruder in slicingData.Extruders) { SliceLayer currentLayer = currentExtruder.Layers[layerIndex]; for (int partIndex = 0; partIndex < currentExtruder.Layers[layerIndex].Islands.Count; partIndex++) { LayerIsland currentIsland = currentLayer.Islands[partIndex]; if (currentIsland.IslandOutline.Count > 0) { layerHasData = true; break; } } } if (layerHasData) { break; } totalLayers--; } } gcode.WriteComment("Layer count: {0}".FormatWith(totalLayers)); // keep the raft generation code inside of raft slicingData.WriteRaftGCodeIfRequired(gcode, config); for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { if (MatterSlice.Canceled) { return; } if(config.outputOnlyFirstLayer && layerIndex > 0) { break; } LogOutput.Log("Writing Layers {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers)); LogOutput.logProgress("export", layerIndex + 1, totalLayers); if (layerIndex == 0) { skirtConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "SKIRT"); inset0Config.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "WALL-OUTER"); insetXConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "WALL-INNER"); fillConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "FILL", false); topFillConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "TOP-FILL", false); bottomFillConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "BOTTOM-FILL", false); airGappedBottomConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "AIR-GAP", false); bridgeConfig.SetData(config.FirstLayerSpeed, config.FirstLayerExtrusionWidth_um, "BRIDGE"); supportNormalConfig.SetData(config.FirstLayerSpeed, config.SupportExtrusionWidth_um, "SUPPORT"); supportInterfaceConfig.SetData(config.FirstLayerSpeed, config.ExtrusionWidth_um, "SUPPORT-INTERFACE"); } else { skirtConfig.SetData(config.InsidePerimetersSpeed, config.ExtrusionWidth_um, "SKIRT"); inset0Config.SetData(config.OutsidePerimeterSpeed, config.OutsideExtrusionWidth_um, "WALL-OUTER"); insetXConfig.SetData(config.InsidePerimetersSpeed, config.ExtrusionWidth_um, "WALL-INNER"); fillConfig.SetData(config.InfillSpeed, config.ExtrusionWidth_um, "FILL", false); topFillConfig.SetData(config.TopInfillSpeed, config.ExtrusionWidth_um, "TOP-FILL", false); bottomFillConfig.SetData(config.InfillSpeed, config.ExtrusionWidth_um, "BOTTOM-FILL", false); airGappedBottomConfig.SetData(config.FirstLayerSpeed, config.ExtrusionWidth_um, "AIR-GAP", false); bridgeConfig.SetData(config.BridgeSpeed, config.ExtrusionWidth_um, "BRIDGE"); supportNormalConfig.SetData(config.SupportMaterialSpeed, config.SupportExtrusionWidth_um, "SUPPORT"); supportInterfaceConfig.SetData(config.FirstLayerSpeed - 1, config.ExtrusionWidth_um, "SUPPORT-INTERFACE"); } gcode.WriteComment("LAYER:{0}".FormatWith(layerIndex)); if (layerIndex == 0) { gcode.SetExtrusion(config.FirstLayerThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); } else { gcode.SetExtrusion(config.LayerThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); } GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); if (layerIndex == 0 && config.RetractionZHop > 0) { gcodeLayer.ForceRetract(); } // get the correct height for this layer int z = config.FirstLayerThickness_um + layerIndex * config.LayerThickness_um; if (config.EnableRaft) { z += config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um + config.RaftSurfaceLayers * config.RaftSurfaceThickness_um; if (layerIndex == 0) { // We only raise the first layer of the print up by the air gap. // To give it: // Less press into the raft // More time to cool // more surface area to air while extruding z += config.RaftAirGap_um; } } gcode.setZ(z); // We only create the skirt if we are on layer 0. if (layerIndex == 0 && !config.ShouldGenerateRaft()) { QueueSkirtToGCode(slicingData, gcodeLayer, layerIndex); } int fanSpeedPercent = GetFanSpeed(layerIndex, gcodeLayer); for (int extruderIndex = 0; extruderIndex < config.MaxExtruderCount(); extruderIndex++) { int prevExtruder = gcodeLayer.GetExtruder(); bool extruderChanged = gcodeLayer.SetExtruder(extruderIndex); if (extruderChanged) { slicingData.PrimeOnWipeTower(extruderIndex, layerIndex, gcodeLayer, fillConfig, config); //Make sure we wipe the old extruder on the wipe tower. gcodeLayer.QueueTravel(slicingData.wipePoint - config.ExtruderOffsets[prevExtruder] + config.ExtruderOffsets[gcodeLayer.GetExtruder()]); } if (layerIndex == 0) { QueueExtruderLayerToGCode(slicingData, gcodeLayer, extruderIndex, layerIndex, config.FirstLayerExtrusionWidth_um, z); } else { QueueExtruderLayerToGCode(slicingData, gcodeLayer, extruderIndex, layerIndex, config.ExtrusionWidth_um, z); } if (slicingData.support != null) { if ((config.SupportExtruder <= 0 && extruderIndex == 0) || config.SupportExtruder == extruderIndex) { slicingData.support.QueueNormalSupportLayer(config, gcodeLayer, layerIndex, supportNormalConfig); } if ((config.SupportInterfaceExtruder <= 0 && extruderIndex == 0) || config.SupportInterfaceExtruder == extruderIndex) { slicingData.support.QueueInterfaceSupportLayer(config, gcodeLayer, layerIndex, supportInterfaceConfig); } } } slicingData.EnsureWipeTowerIsSolid(layerIndex, gcodeLayer, fillConfig, config); if (slicingData.support != null) { z += config.SupportAirGap_um; gcode.setZ(z); gcodeLayer.QueueTravel(gcodeLayer.LastPosition); for (int extruderIndex = 0; extruderIndex < slicingData.Extruders.Count; extruderIndex++) { QueueAirGappedExtruderLayerToGCode(slicingData, gcodeLayer, extruderIndex, layerIndex, config.ExtrusionWidth_um, z); } slicingData.support.QueueAirGappedBottomLayer(config, gcodeLayer, layerIndex, airGappedBottomConfig); } //Finish the layer by applying speed corrections for minimum layer times. gcodeLayer.ForceMinimumLayerTime(config.MinimumLayerTimeSeconds, config.MinimumPrintingSpeed); gcode.WriteFanCommand(fanSpeedPercent); int currentLayerThickness_um = config.LayerThickness_um; if (layerIndex <= 0) { currentLayerThickness_um = config.FirstLayerThickness_um; } // Move to the best point for the next layer if (!config.ContinuousSpiralOuterPerimeter && layerIndex > 0 && layerIndex < totalLayers - 2) { // 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 SliceLayer layer = slicingData?.Extruders?[0]?.Layers?[layerIndex + 1]; if (layer.Islands.Count == 1) { LayerIsland island = layer?.Islands?[0]; if (island?.InsetToolPaths?[0]?[0]?.Count > 0) { int bestPoint = PathOrderOptimizer.GetBestIndex(island.InsetToolPaths[0][0], config.ExtrusionWidth_um); gcodeLayer.SetOuterPerimetersToAvoidCrossing(island.AvoidCrossingBoundary); gcodeLayer.QueueTravel(island.InsetToolPaths[0][0][bestPoint]); // Now move up to the next layer so we don't start the extrusion one layer too low. gcode.setZ(z + config.LayerThickness_um); gcodeLayer.QueueTravel(island.InsetToolPaths[0][0][bestPoint]); } } } gcodeLayer.WriteQueuedGCode(currentLayerThickness_um, fanSpeedPercent, config.BridgeFanSpeedPercent); } LogOutput.Log("Wrote layers in {0:0.00}s.\n".FormatWith(timeKeeper.Elapsed.TotalSeconds)); timeKeeper.Restart(); gcode.TellFileSize(); gcode.WriteFanCommand(0); //Store the object height for when we are printing multiple objects, as we need to clear every one of them when moving to the next position. maxObjectHeight = Math.Max(maxObjectHeight, slicingData.modelSize.z); }
private void QueueSkirtToGCode(LayerDataStorage slicingData, GCodePlanner gcodeLayer, int layerIndex) { if (slicingData.skirt.Count > 0 && slicingData.skirt[0].Count > 0) { IntPoint lowestPoint = slicingData.skirt[0][0]; // lets make sure we start with the most outside loop foreach (Polygon polygon in slicingData.skirt) { foreach (IntPoint position in polygon) { if (position.Y < lowestPoint.Y) { lowestPoint = polygon[0]; } } } gcodeLayer.QueueTravel(lowestPoint); } gcodeLayer.QueuePolygonsByOptimizer(slicingData.skirt, skirtConfig); }
private static Polygons GetSkirtBounds(ConfigSettings config, LayerDataStorage storage, bool externalOnly, int distance, int extrusionWidth_um, int brimCount) { bool hasWipeTower = storage.wipeTower.PolygonLength() > 0; Polygons skirtPolygons = new Polygons(); if (config.EnableRaft) { skirtPolygons = skirtPolygons.CreateUnion(storage.raftOutline); } else { Polygons allOutlines = hasWipeTower ? new Polygons(storage.wipeTower) : new Polygons(); // Loop over every extruder for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { // Only process the first extruder on spiral vase or // skip extruders that have empty layers if (config.ContinuousSpiralOuterPerimeter) { SliceLayer layer0 = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer0.Islands[0]?.IslandOutline); } else { // Add the layers outline to allOutlines SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; foreach (var island in layer.Islands) { if (island.IslandOutline?.Count > 0) { allOutlines.Add(island.IslandOutline[0]); } } } } if (brimCount > 0) { Polygons unionedIslandOutlines = new Polygons(); // Grow each island by the current brim distance // Union the island brims unionedIslandOutlines = unionedIslandOutlines.CreateUnion(allOutlines); if (storage.support != null) { unionedIslandOutlines = unionedIslandOutlines.CreateUnion(storage.support.GetBedOutlines()); } Polygons brimLoops = new Polygons(); // Loop over the requested brimCount creating and unioning a new perimeter for each island for (int brimIndex = 0; brimIndex < brimCount; brimIndex++) { int offsetDistance = extrusionWidth_um * brimIndex + extrusionWidth_um / 2; // Extend the polygons to account for the brim (ensures convex hull takes this data into account) brimLoops.AddAll(unionedIslandOutlines.Offset(offsetDistance)); } // TODO: This is a quick hack, reuse the skirt data to stuff in the brim. Good enough from proof of concept storage.skirt.AddAll(brimLoops); skirtPolygons = skirtPolygons.CreateUnion(brimLoops); } else { skirtPolygons = skirtPolygons.CreateUnion(allOutlines); } if (storage.support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.support.GetBedOutlines()); } } return(skirtPolygons); }
//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); } PathOrderOptimizer partOrderOptimizer = new PathOrderOptimizer(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 partIndex = 0; partIndex < partOrderOptimizer.bestPolygonOrderIndex.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } LayerIsland island = layer.Islands[partOrderOptimizer.bestPolygonOrderIndex[partIndex]]; 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); gcodeLayer.QueuePolygonsByOptimizer(bridgePolygons, bridgConfig); } if (config.numberOfPerimeters > 0) { if (partIndex != lastPartIndex) { // force a retract if changing islands if (config.retractWhenChangingIslands) { gcodeLayer.ForceRetract(); } lastPartIndex = partIndex; } 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 = PathOrderOptimizer.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); }
private static Polygons GetSkirtBounds(ConfigSettings config, LayerDataStorage storage, bool externalOnly, int distance, int extrusionWidth_um, int brimCount) { bool hasWipeTower = storage.wipeTower.PolygonLength() > 0; Polygons skirtPolygons = new Polygons(); if (config.EnableRaft) { skirtPolygons = skirtPolygons.CreateUnion(storage.raftOutline); } else { Polygons allOutlines = hasWipeTower ? new Polygons(storage.wipeTower.Offset(-extrusionWidth_um / 2)) : new Polygons(); if (storage.wipeShield.Count > 0 && storage.wipeShield[0].Count > 0) { allOutlines = allOutlines.CreateUnion(storage.wipeShield[0].Offset(-extrusionWidth_um / 2)); } // Loop over every extruder for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { // Only process the first extruder on spiral vase or // skip extruders that have empty layers if (config.ContinuousSpiralOuterPerimeter) { SliceLayer layer0 = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer0.Islands[0]?.IslandOutline); } else { // Add the layers outline to allOutlines SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; foreach (var island in layer.Islands) { if (island.IslandOutline?.Count > 0) { allOutlines.Add(island.IslandOutline[0]); } } } } if (brimCount > 0) { Polygons brimIslandOutlines = new Polygons(); // Grow each island by the current brim distance // Union the island brims brimIslandOutlines = brimIslandOutlines.CreateUnion(allOutlines); if (storage.support != null) { brimIslandOutlines = brimIslandOutlines.CreateUnion(storage.support.GetBedOutlines()); } brimIslandOutlines = brimIslandOutlines.Offset(extrusionWidth_um * (brimCount - 1)); // Loop over the requested brimCount creating and unioning a new perimeter for each island List <Polygons> brimIslands = brimIslandOutlines.ProcessIntoSeparateIslands(); foreach (var brimIsland in brimIslands) { Polygons brimLoops = new Polygons(); for (int brimIndex = brimCount - 1; brimIndex >= 0; brimIndex--) { int offsetDistance = extrusionWidth_um * brimIndex; // Extend the polygons to account for the brim (ensures convex hull takes this data into account) brimLoops.AddAll(brimIsland.Offset(-offsetDistance + extrusionWidth_um / 2)); } storage.Brims.Add(brimLoops); } // and extend the bonuds of the skirt polygons skirtPolygons = skirtPolygons.CreateUnion(brimIslandOutlines); } skirtPolygons = skirtPolygons.CreateUnion(allOutlines); if (storage.support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.support.GetBedOutlines()); } } return(skirtPolygons); }
//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, 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]; PathOrderOptimizer partOrderOptimizer = new PathOrderOptimizer(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.AvoidCrossingBoundary); } 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, bottomFillPolygons, fillPolygons, topFillPolygons, bridgePolygons); bottomFillIslandPolygons.Add(bottomFillPolygons); #if DEBUG if (bridgePolygons.Count > 0) { new Exception("Unexpected bridge polygons in air gapped region"); } #endif if (config.NumberOfPerimeters > 0) { if (inlandIndex != lastPartIndex) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { 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 insetIndex = 1; insetIndex < part.InsetToolPaths.Count; insetIndex++) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, part.InsetToolPaths[insetIndex], 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); }