private void processSliceData(SliceDataStorage storage) { if (config.continuousSpiralOuterPerimeter) { config.numberOfTopLayers = 0; config.infillPercent = 0; } MultiVolumes.RemoveVolumesIntersections(storage.volumes); MultiVolumes.OverlapMultipleVolumesSlightly(storage.volumes, config.multiVolumeOverlapPercent); #if False LayerPart.dumpLayerparts(storage, "output.html"); #endif int totalLayers = storage.volumes[0].layers.Count; #if DEBUG for (int volumeIndex = 1; volumeIndex < storage.volumes.Count; volumeIndex++) { if (totalLayers != storage.volumes[volumeIndex].layers.Count) { throw new Exception("All the valumes must have the same number of layers (they just can have empty layers)."); } } #endif for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++) { 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 += 5; } SliceLayer layer = storage.volumes[volumeIndex].layers[layerIndex]; int extrusionWidth = config.extrusionWidth_um; if (layerIndex == 0) { extrusionWidth = config.firstLayerExtrusionWidth_um; } Inset.generateInsets(layer, extrusionWidth, insetCount); } LogOutput.Log("Creating Insets {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers)); } if (config.wipeShieldDistanceFromShapes_um > 0) { CreateWipeShields(storage, totalLayers); } LogOutput.Log("Generated inset in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.Seconds)); 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 choosen. if (!config.continuousSpiralOuterPerimeter || (int)(layerIndex) < config.numberOfBottomLayers) { for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++) { int extrusionWidth = config.extrusionWidth_um; if (layerIndex == 0) { extrusionWidth = config.firstLayerExtrusionWidth_um; } TopsAndBottoms.GenerateTopAndBottom(layerIndex, storage.volumes[volumeIndex], extrusionWidth, 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.Seconds)); timeKeeper.Restart(); if (config.wipeTowerSize_um > 0) { Polygon p = new Polygon(); storage.wipeTower.Add(p); p.Add(new IntPoint(storage.modelMin.x - 3000, storage.modelMax.y + 3000)); p.Add(new IntPoint(storage.modelMin.x - 3000, storage.modelMax.y + 3000 + config.wipeTowerSize_um)); p.Add(new IntPoint(storage.modelMin.x - 3000 - config.wipeTowerSize_um, storage.modelMax.y + 3000 + config.wipeTowerSize_um)); p.Add(new IntPoint(storage.modelMin.x - 3000 - config.wipeTowerSize_um, storage.modelMax.y + 3000)); storage.wipePoint = new IntPoint(storage.modelMin.x - 3000 - config.wipeTowerSize_um / 2, storage.modelMax.y + 3000 + config.wipeTowerSize_um / 2); } if (config.enableRaft) { Raft.GenerateRaftOutlines(storage, config.raftExtraDistanceAroundPart_um, config); Skirt.generateSkirt(storage, config.skirtDistance_um + config.raftBaseLineSpacing_um, config.raftBaseLineSpacing_um, config.numberOfSkirtLoops, config.skirtMinLength_um, config.raftBaseThickness_um, config); } else { Skirt.generateSkirt(storage, config.skirtDistance_um, config.firstLayerExtrusionWidth_um, config.numberOfSkirtLoops, config.skirtMinLength_um, config.firstLayerThickness_um, config); } }
private void writeGCode(SliceDataStorage storage) { 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); if (gcode.GetOutputType() == ConfigConstants.OUTPUT_TYPE.BFB) { gcode.WriteComment("enable auto-retraction"); gcode.WriteLine("M227 S{0} P{1}".FormatWith(config.retractionOnTravel * 2560, config.retractionOnTravel * 2560)); } } else { gcode.WriteFanCommand(0); gcode.ResetExtrusionValue(); gcode.WriteRetraction(); gcode.setZ(maxObjectHeight + 5000); gcode.WriteMove(gcode.GetPositionXY(), config.travelSpeed, 0); gcode.WriteMove(new IntPoint(storage.modelMin.x, storage.modelMin.y), config.travelSpeed, 0); } fileNumber++; int totalLayers = storage.volumes[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 (SliceVolumeStorage currentVolume in storage.volumes) { SliceLayer currentLayer = currentVolume.layers[layerIndex]; for (int partIndex = 0; partIndex < currentVolume.layers[layerIndex].parts.Count; partIndex++) { SliceLayerPart currentPart = currentLayer.parts[partIndex]; if (currentPart.TotalOutline.Count > 0) { layerHasData = true; break; } } } if (layerHasData) { break; } totalLayers--; } } gcode.WriteComment("Layer count: {0}".FormatWith(totalLayers)); // keep the raft generation code inside of raft Raft.GenerateRaftGCodeIfRequired(storage, config, gcode); int volumeIndex = 0; 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); int extrusionWidth_um = config.extrusionWidth_um; if (layerIndex == 0) { extrusionWidth_um = config.firstLayerExtrusionWidth_um; } if (layerIndex == 0) { skirtConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "SKIRT"); inset0Config.setData(config.firstLayerSpeed, extrusionWidth_um, "WALL-OUTER"); insetXConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "WALL-INNER"); fillConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "FILL", false); bridgConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "BRIDGE"); supportNormalConfig.setData(config.firstLayerSpeed, config.supportExtrusionWidth_um, "SUPPORT"); supportInterfaceConfig.setData(config.firstLayerSpeed, config.extrusionWidth_um, "SUPPORT-INTERFACE"); } else { skirtConfig.setData(config.insidePerimetersSpeed, extrusionWidth_um, "SKIRT"); inset0Config.setData(config.outsidePerimeterSpeed, extrusionWidth_um, "WALL-OUTER"); insetXConfig.setData(config.insidePerimetersSpeed, extrusionWidth_um, "WALL-INNER"); fillConfig.setData(config.infillSpeed, extrusionWidth_um, "FILL", false); bridgConfig.setData(config.bridgeSpeed, extrusionWidth_um, "BRIDGE"); supportNormalConfig.setData(config.supportMaterialSpeed, config.supportExtrusionWidth_um, "SUPPORT"); supportInterfaceConfig.setData(config.supportMaterialSpeed, 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 and the first volume and there is no raft. if (layerIndex == 0 && volumeIndex == 0 && !Raft.ShouldGenerateRaft(config)) { AddSkirtToGCode(storage, gcodeLayer, volumeIndex, layerIndex); } bool printSupportFirst = (storage.support.generated && config.supportExtruder >= 0 && config.supportExtruder == gcodeLayer.getExtruder()); if (printSupportFirst) { AddSupportToGCode(storage, gcodeLayer, layerIndex, config); } int fanSpeedPercent = GetFanSpeed(layerIndex, gcodeLayer); for (int volumeCnt = 0; volumeCnt < storage.volumes.Count; volumeCnt++) { if (volumeCnt > 0) { volumeIndex = (volumeIndex + 1) % storage.volumes.Count; } AddVolumeLayerToGCode(storage, gcodeLayer, volumeIndex, layerIndex, extrusionWidth_um, fanSpeedPercent); } if (!printSupportFirst) { AddSupportToGCode(storage, gcodeLayer, layerIndex, config); } //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.WriteGCode(config.doCoolHeadLift, currentLayerThickness_um); } LogOutput.Log("Wrote layers in {0:0.00}s.\n".FormatWith(timeKeeper.Elapsed.Seconds)); 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, storage.modelSize.z); }
//Add a single layer from a single mesh-volume to the GCode void AddVolumeLayerToGCode(SliceDataStorage storage, GCodePlanner gcodeLayer, int volumeIndex, int layerIndex, int extrusionWidth_um, int fanSpeedPercent) { int prevExtruder = gcodeLayer.getExtruder(); bool extruderChanged = gcodeLayer.setExtruder(volumeIndex); if (layerIndex == 0 && volumeIndex == 0 && !Raft.ShouldGenerateRaft(config)) { if (storage.skirt.Count > 0 && storage.skirt[0].Count > 0) { IntPoint lowestPoint = storage.skirt[0][0]; // lets make sure we start with the most outside loop foreach (Polygon polygon in storage.skirt) { foreach (IntPoint position in polygon) { if (position.Y < lowestPoint.Y) { lowestPoint = polygon[0]; } } } gcodeLayer.writeTravel(lowestPoint); } gcodeLayer.writePolygonsByOptimizer(storage.skirt, skirtConfig); } SliceLayer layer = storage.volumes[volumeIndex].layers[layerIndex]; if (extruderChanged) { addWipeTower(storage, gcodeLayer, layerIndex, prevExtruder, extrusionWidth_um); } if (storage.wipeShield.Count > 0 && storage.volumes.Count > 1) { gcodeLayer.setAlwaysRetract(true); gcodeLayer.writePolygonsByOptimizer(storage.wipeShield[layerIndex], skirtConfig); gcodeLayer.setAlwaysRetract(!config.avoidCrossingPerimeters); } PathOrderOptimizer partOrderOptimizer = new PathOrderOptimizer(new IntPoint()); for (int partIndex = 0; partIndex < layer.parts.Count; partIndex++) { partOrderOptimizer.AddPolygon(layer.parts[partIndex].insets[0][0]); } partOrderOptimizer.Optimize(); for (int partCounter = 0; partCounter < partOrderOptimizer.bestPolygonOrderIndex.Count; partCounter++) { SliceLayerPart part = layer.parts[partOrderOptimizer.bestPolygonOrderIndex[partCounter]]; if (config.avoidCrossingPerimeters) { gcodeLayer.SetOuterPerimetersToAvoidCrossing(part.combBoundery); } else { gcodeLayer.setAlwaysRetract(true); } Polygons fillPolygons = new Polygons(); Polygons bridgePolygons = new Polygons(); CalculateInfillData(storage, volumeIndex, layerIndex, extrusionWidth_um, part, ref fillPolygons, ref bridgePolygons); // Write the bidge polgons 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.writePolygonsByOptimizer(bridgePolygons, bridgConfig); gcode.writeFanCommand(fanSpeedPercent); } if (config.numberOfPerimeters > 0) { if (partCounter > 0) { gcodeLayer.forceRetract(); } if (config.continuousSpiralOuterPerimeter) { if (layerIndex >= config.numberOfBottomLayers) { inset0Config.spiralize = true; } if (layerIndex == config.numberOfBottomLayers && part.insets.Count > 0) { gcodeLayer.writePolygonsByOptimizer(part.insets[0], insetXConfig); } } // If we are on the very first layer we start with the outside in so that we can stick to the bed better. if (config.outsidePerimetersFirst || layerIndex == 0) { // First the outside (this helps with accuracy) if (part.insets.Count > 0) { gcodeLayer.writePolygonsByOptimizer(part.insets[0], inset0Config); } for (int perimeterIndex = 1; perimeterIndex < part.insets.Count; perimeterIndex++) { gcodeLayer.writePolygonsByOptimizer(part.insets[perimeterIndex], insetXConfig); } } else // This is so we can do overhanges better (the outside can stick a bit to the inside). { // 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.insets.Count; perimeterIndex++) { gcodeLayer.writePolygonsByOptimizer(part.insets[perimeterIndex], insetXConfig); } // then 0 if (part.insets.Count > 0) { gcodeLayer.writePolygonsByOptimizer(part.insets[0], inset0Config); } } } gcodeLayer.writePolygonsByOptimizer(fillPolygons, fillConfig); //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); }