public void QueueAirGappedBottomLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportNormalConfig) { // normal support Polygons currentAirGappedBottoms = airGappedBottomOutlines[layerIndex]; currentAirGappedBottoms = currentAirGappedBottoms.Offset(-config.ExtrusionWidth_um / 2); List <Polygons> supportIslands = currentAirGappedBottoms.ProcessIntoSeparatIslands(); foreach (Polygons islandOutline in supportIslands) { Polygons islandInfillLines = new Polygons(); // render a grid of support if (config.GenerateSupportPerimeter) { Polygons outlines = Clipper.CleanPolygons(islandOutline, config.ExtrusionWidth_um / 4); gcodeLayer.QueuePolygonsByOptimizer(outlines, supportNormalConfig); } Polygons infillOutline = islandOutline.Offset(-config.ExtrusionWidth_um / 2); switch (config.SupportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; } gcodeLayer.QueuePolygonsByOptimizer(islandInfillLines, supportNormalConfig); } }
public bool QueueNormalSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportNormalConfig) { // normal support Polygons currentSupportOutlines = supportOutlines[layerIndex]; currentSupportOutlines = currentSupportOutlines.Offset(-supportNormalConfig.lineWidth_um / 2); List <Polygons> supportIslands = currentSupportOutlines.ProcessIntoSeparatIslands(); bool outputPaths = false; foreach (Polygons islandOutline in supportIslands) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } Polygons islandInfillLines = new Polygons(); // render a grid of support if (config.GenerateSupportPerimeter || layerIndex == 0) { Polygons outlines = Clipper.CleanPolygons(islandOutline, config.ExtrusionWidth_um / 4); if (gcodeLayer.QueuePolygonsByOptimizer(outlines, supportNormalConfig)) { outputPaths = true; } } Polygons infillOutline = islandOutline.Offset(-supportNormalConfig.lineWidth_um / 2); if (layerIndex == 0) { // on the first layer print this as solid Infill.GenerateLineInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.ExtrusionWidth_um); } else { switch (config.SupportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; } } if (gcodeLayer.QueuePolygonsByOptimizer(islandInfillLines, supportNormalConfig)) { outputPaths |= true; } } return(outputPaths); }
public void QueueInterfaceSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportInterfaceConfig) { // interface Polygons currentInterfaceOutlines = interfaceLayers[layerIndex].Offset(-config.ExtrusionWidth_um / 2); if (currentInterfaceOutlines.Count > 0) { Polygons supportLines = new Polygons(); Infill.GenerateLineInfill(config, currentInterfaceOutlines, supportLines, config.InfillStartingAngle + 90, config.ExtrusionWidth_um); gcodeLayer.QueuePolygonsByOptimizer(supportLines, supportInterfaceConfig); } }
private void AddSupportToGCode(SliceDataStorage storage, GCodePlanner gcodeLayer, int layerIndex, ConfigSettings config) { if (!storage.support.generated) { return; } if (config.supportExtruder > -1) { int prevExtruder = gcodeLayer.getExtruder(); if (gcodeLayer.SetExtruder(config.supportExtruder)) { addWipeTower(storage, gcodeLayer, layerIndex, prevExtruder, config.extrusionWidth_um); } if (storage.wipeShield.Count > 0 && storage.volumes.Count == 1) { gcodeLayer.SetAlwaysRetract(true); gcodeLayer.WritePolygonsByOptimizer(storage.wipeShield[layerIndex], skirtConfig); gcodeLayer.SetAlwaysRetract(config.avoidCrossingPerimeters); } } int currentZHeight_um = config.firstLayerThickness_um; if (layerIndex == 0) { currentZHeight_um /= 2; } else { if (layerIndex > 1) { currentZHeight_um += (layerIndex - 1) * config.layerThickness_um; } currentZHeight_um += config.layerThickness_um / 2; } SupportPolyGenerator supportGenerator = new SupportPolyGenerator(storage.support, currentZHeight_um); WriteSupportPolygons(storage, gcodeLayer, layerIndex, config, supportGenerator.supportPolygons, SupportType.General); if (config.supportInterfaceExtruder != -1 && config.supportInterfaceExtruder != config.supportExtruder) { gcodeLayer.SetExtruder(config.supportInterfaceExtruder); } WriteSupportPolygons(storage, gcodeLayer, layerIndex, config, supportGenerator.interfacePolygons, SupportType.Interface); }
public void PrimeOnWipeTower(int extruderIndex, int layerIndex, GCodePlanner gcodeLayer, GCodePathConfig fillConfig, ConfigSettings config) { if (!HaveWipeTower(config) || layerIndex > LastLayerWithChange(config) + 1) { return; } //If we changed extruder, print the wipe/prime tower for this nozzle; Polygons fillPolygons = new Polygons(); GenerateWipeTowerInfill(extruderIndex, this.wipeTower, fillPolygons, fillConfig.lineWidth_um, config); gcodeLayer.QueuePolygons(fillPolygons, fillConfig); extrudersThatHaveBeenPrimed[extruderIndex] = true; }
private void addWipeTower(SliceDataStorage storage, 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.WritePolygonsByOptimizer(storage.wipeTower, supportInterfaceConfig); Polygons fillPolygons = new Polygons(); Infill.GenerateLinePaths(storage.wipeTower, ref fillPolygons, extrusionWidth_um, config.infillExtendIntoPerimeter_um, 45 + 90 * (layerNr % 2)); gcodeLayer.WritePolygonsByOptimizer(fillPolygons, supportInterfaceConfig); //Make sure we wipe the old extruder on the wipe tower. gcodeLayer.WriteTravel(storage.wipePoint - config.extruderOffsets[prevExtruder] + config.extruderOffsets[gcodeLayer.getExtruder()]); }
private int GetFanSpeed(int layerIndex, GCodePlanner gcodeLayer) { int fanSpeedPercent = config.fanSpeedMinPercent; if (gcodeLayer.getExtrudeSpeedFactor() <= 50) { fanSpeedPercent = config.fanSpeedMaxPercent; } else { int n = gcodeLayer.getExtrudeSpeedFactor() - 50; fanSpeedPercent = config.fanSpeedMinPercent * n / 50 + config.fanSpeedMaxPercent * (50 - n) / 50; } if (layerIndex < config.firstLayerToAllowFan) { // Don't allow the fan below this layer fanSpeedPercent = 0; } return fanSpeedPercent; }
public void EnsureWipeTowerIsSolid(int layerIndex, GCodePlanner gcodeLayer, GCodePathConfig fillConfig, ConfigSettings config) { if (layerIndex >= LastLayerWithChange(config) || extrudersThatHaveBeenPrimed == null) { return; } // print all of the extruder loops that have not already been printed for (int extruderIndex = 0; extruderIndex < config.MaxExtruderCount(); extruderIndex++) { if (!extrudersThatHaveBeenPrimed[extruderIndex]) { // write the loops for this extruder, but don't change to it. We are just filling the prime tower. PrimeOnWipeTower(extruderIndex, 0, gcodeLayer, fillConfig, config); } // clear the history of printer extruders for the next layer extrudersThatHaveBeenPrimed[extruderIndex] = false; } }
public bool QueueInterfaceSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportInterfaceConfig) { // interface bool outputPaths = false; Polygons currentInterfaceOutlines2 = interfaceLayers[layerIndex].Offset(-config.ExtrusionWidth_um / 2); if (currentInterfaceOutlines2.Count > 0) { List <Polygons> interfaceIslands = currentInterfaceOutlines2.ProcessIntoSeparatIslands(); foreach (Polygons interfaceOutline in interfaceIslands) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } // make a border if layer 0 if (layerIndex == 0) { Polygons infillOutline = interfaceOutline.Offset(-supportInterfaceConfig.lineWidth_um / 2); Polygons outlines = Clipper.CleanPolygons(infillOutline, config.ExtrusionWidth_um / 4); if (gcodeLayer.QueuePolygonsByOptimizer(outlines, supportInterfaceConfig)) { outputPaths = true; } } Polygons supportLines = new Polygons(); Infill.GenerateLineInfill(config, interfaceOutline, supportLines, config.InfillStartingAngle + 90, config.ExtrusionWidth_um); if (gcodeLayer.QueuePolygonsByOptimizer(supportLines, supportInterfaceConfig)) { outputPaths = true; } } } return(outputPaths); }
public void QueueInterfaceSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportInterfaceConfig) { // interface Polygons currentInterfaceOutlines2 = interfaceLayers[layerIndex].Offset(-config.ExtrusionWidth_um / 2); if (currentInterfaceOutlines2.Count > 0) { List <Polygons> interfaceIslands = currentInterfaceOutlines2.ProcessIntoSeparatIslands(); foreach (Polygons interfaceOutline in interfaceIslands) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } Polygons supportLines = new Polygons(); Infill.GenerateLineInfill(config, interfaceOutline, supportLines, config.InfillStartingAngle + 90, config.ExtrusionWidth_um); gcodeLayer.QueuePolygonsByOptimizer(supportLines, supportInterfaceConfig); } } }
private void AddSkirtToGCode(SliceDataStorage storage, GCodePlanner gcodeLayer, int volumeIndex, int layerIndex) { 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); }
public void QueueNormalSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportNormalConfig, GCodePathConfig supportInterfaceConfig) { // normal support Polygons currentSupportOutlines = supportOutlines[layerIndex]; currentSupportOutlines = currentSupportOutlines.Offset(-supportNormalConfig.lineWidth_um / 2); List<Polygons> supportIslands = currentSupportOutlines.ProcessIntoSeparatIslands(); foreach (Polygons islandOutline in supportIslands) { Polygons islandInfillLines = new Polygons(); // render a grid of support if (config.generateSupportPerimeter) { gcodeLayer.QueuePolygonsByOptimizer(islandOutline, supportNormalConfig); } Polygons infillOutline = islandOutline.Offset(-supportNormalConfig.lineWidth_um / 2); switch (config.supportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, infillOutline, ref islandInfillLines, config.supportInfillStartingAngle, config.supportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, infillOutline, ref islandInfillLines, config.supportInfillStartingAngle, config.supportLineSpacing_um); break; } gcodeLayer.QueuePolygonsByOptimizer(islandInfillLines, supportNormalConfig); } // interface Polygons currentInterfaceOutlines = interfaceLayers[layerIndex].Offset(-config.extrusionWidth_um / 2); if (currentInterfaceOutlines.Count > 0) { Polygons supportLines = new Polygons(); Infill.GenerateLineInfill(config, currentInterfaceOutlines, ref supportLines, config.infillStartingAngle + 90, config.extrusionWidth_um); gcodeLayer.QueuePolygonsByOptimizer(supportLines, supportInterfaceConfig); } }
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 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); }
//Add a single layer from a single mesh-volume to the GCode private void AddVolumeLayerToGCode(SliceDataStorage storage, GCodePlanner gcodeLayer, int volumeIndex, int layerIndex, int extrusionWidth_um, int fanSpeedPercent) { int prevExtruder = gcodeLayer.getExtruder(); bool extruderChanged = gcodeLayer.SetExtruder(volumeIndex); 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++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } partOrderOptimizer.AddPolygon(layer.parts[partIndex].Insets[0][0]); } partOrderOptimizer.Optimize(); for (int partIndex = 0; partIndex < partOrderOptimizer.bestPolygonOrderIndex.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } SliceLayerPart part = layer.parts[partOrderOptimizer.bestPolygonOrderIndex[partIndex]]; if (config.avoidCrossingPerimeters) { gcodeLayer.SetOuterPerimetersToAvoidCrossing(part.AvoidCrossingBoundery); } else { gcodeLayer.SetAlwaysRetract(true); } Polygons fillPolygons = new Polygons(); Polygons bridgePolygons = new Polygons(); CalculateInfillData(storage, volumeIndex, layerIndex, part, ref fillPolygons, ref bridgePolygons); // Write the bridge 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 (partIndex != lastPartIndex) { // force a retract if changing islands 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 in so that we can stick to the bed better. if (config.outsidePerimetersFirst || layerIndex == 0 || inset0Config.spiralize) { // First the outside (this helps with accuracy) if (part.Insets.Count > 0) { gcodeLayer.WritePolygonsByOptimizer(part.Insets[0], inset0Config); } if (!inset0Config.spiralize) { 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); }
private bool QueueClosetsInset(Polygons insetsConsider, bool limitDistance, GCodePathConfig pathConfig, int layerIndex, GCodePlanner gcodeLayer) { long maxDist_um = long.MaxValue; if (limitDistance) { maxDist_um = config.ExtrusionWidth_um * 4; } int polygonPrintedIndex = -1; for (int polygonIndex = 0; polygonIndex < insetsConsider.Count; polygonIndex++) { Polygon currentPolygon = insetsConsider[polygonIndex]; int bestPoint = PathOrderOptimizer.GetClosestIndex(currentPolygon, gcodeLayer.LastPosition); if (bestPoint > -1) { long distance = (currentPolygon[bestPoint] - gcodeLayer.LastPosition).Length(); if (distance < maxDist_um) { maxDist_um = distance; polygonPrintedIndex = polygonIndex; } } } if (polygonPrintedIndex > -1) { QueuePolygonsConsideringSupport(layerIndex, gcodeLayer, new Polygons() { insetsConsider[polygonPrintedIndex] }, pathConfig, SupportWriteType.UnsupportedAreas); insetsConsider.RemoveAt(polygonPrintedIndex); return true; } // Return the original limitDistance value if we didn't match a polygon return limitDistance; }
public static void GenerateRaftGCodeIfRequired(SliceDataStorage storage, ConfigSettings config, GCodeExport gcode) { if (ShouldGenerateRaft(config)) { GCodePathConfig raftBaseConfig = new GCodePathConfig(config.firstLayerSpeed, config.extrusionWidth_um * 3, "SUPPORT"); GCodePathConfig raftMiddleConfig = new GCodePathConfig(config.raftPrintSpeed, config.raftInterfaceLinewidth_um, "SUPPORT"); GCodePathConfig raftSurfaceConfig = new GCodePathConfig((config.raftSurfacePrintSpeed > 0) ? config.raftSurfacePrintSpeed : config.raftPrintSpeed, config.raftSurfaceLinewidth_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.supportExtruder > 0) { gcodeLayer.setExtruder(config.supportExtruder); } gcode.setZ(config.raftBaseThickness_um); gcode.setExtrusion(config.raftBaseThickness_um, config.filamentDiameter_um, config.extrusionMultiplier); gcodeLayer.writePolygonsByOptimizer(storage.raftOutline, raftBaseConfig); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, ref raftLines, config.raftBaseThickness_um, config.raftLineSpacing_um, config.infillExtendIntoPerimeter_um, 0); gcodeLayer.writePolygonsByOptimizer(storage.skirt, raftBaseConfig); gcodeLayer.writePolygonsByOptimizer(raftLines, raftBaseConfig); gcodeLayer.writeGCode(false, 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, ref raftLines, config.raftInterfaceLinewidth_um, config.raftInterfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, 45); gcodeLayer.writePolygonsByOptimizer(raftLines, raftMiddleConfig); gcodeLayer.writeGCode(false, config.raftInterfaceThicknes_um); } for (int raftSurfaceLayer = 1; raftSurfaceLayer <= config.raftSurfaceLayers; raftSurfaceLayer++) { 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 * raftSurfaceLayer); gcode.setExtrusion(config.raftSurfaceThickness_um, config.filamentDiameter_um, config.extrusionMultiplier); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, ref raftLines, config.raftSurfaceLinewidth_um, config.raftSurfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, 90 * raftSurfaceLayer); gcodeLayer.writePolygonsByOptimizer(raftLines, raftSurfaceConfig); gcodeLayer.writeGCode(false, config.raftInterfaceThicknes_um); } } }
private void QueuePolygonsConsideringSupport(int layerIndex, GCodePlanner gcodeLayer, Polygons polygonsToWrite, GCodePathConfig fillConfig, SupportWriteType supportWriteType) { if (config.generateSupport && layerIndex > 0 && !config.continuousSpiralOuterPerimeter) { Polygons supportOutlines = slicingData.support.GetRequiredSupportAreas(layerIndex); if (supportWriteType == SupportWriteType.UnsupportedAreas) { if (supportOutlines.Count > 0) { Polygons polygonsNotOnSupport; // don't write the bottoms that are sitting on supported areas (they will be written at air gap distance later). polygonsToWrite = PolygonsHelper.ConvertToLines(polygonsToWrite); polygonsNotOnSupport = polygonsToWrite.CreateLineDifference(supportOutlines); gcodeLayer.QueuePolygonsByOptimizer(polygonsNotOnSupport, fillConfig); } else { gcodeLayer.QueuePolygonsByOptimizer(polygonsToWrite, fillConfig); } } else { if (supportOutlines.Count > 0) { if (supportOutlines.Count > 0) { // write the bottoms that are sitting on supported areas. Polygons polygonsOnSupport; polygonsToWrite = PolygonsHelper.ConvertToLines(polygonsToWrite); polygonsOnSupport = supportOutlines.CreateLineIntersections(polygonsToWrite); // ensure that all the segments have only 2 points foreach(Polygon poly in polygonsOnSupport) { while(poly.Count > 2) { // This is an error and I'm not sure why it happened. It needs to be investigated. // LBB 2016 01 12 poly.RemoveAt(poly.Count - 1); } } gcodeLayer.QueuePolygonsByOptimizer(polygonsOnSupport, fillConfig); } else { gcodeLayer.QueuePolygonsByOptimizer(polygonsToWrite, fillConfig); } } } } else if (supportWriteType == SupportWriteType.UnsupportedAreas) { gcodeLayer.QueuePolygonsByOptimizer(polygonsToWrite, fillConfig); } }
//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 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 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 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); }
public static void GenerateRaftGCodeIfRequired(SliceDataStorage storage, ConfigSettings config, GCodeExport gcode) { if (ShouldGenerateRaft(config)) { 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); gcodeLayer.writePolygonsByOptimizer(storage.raftOutline, raftBaseConfig); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, ref raftLines, config.raftBaseLineSpacing_um, config.infillExtendIntoPerimeter_um, 0); gcodeLayer.writePolygonsByOptimizer(storage.skirt, raftBaseConfig); gcodeLayer.writePolygonsByOptimizer(raftLines, raftBaseConfig); gcodeLayer.writeGCode(false, 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, ref raftLines, config.raftInterfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, 45); gcodeLayer.writePolygonsByOptimizer(raftLines, raftMiddleConfig); gcodeLayer.writeGCode(false, 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, ref raftLines, config.raftSurfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle + 90); } else { Infill.GenerateLinePaths(storage.raftOutline, ref raftLines, config.raftSurfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, 90 * raftSurfaceIndex); } gcodeLayer.writePolygonsByOptimizer(raftLines, raftSurfaceConfig); gcodeLayer.writeGCode(false, config.raftInterfaceThicknes_um); } } }
public void EnsureWipeTowerIsSolid(int layerIndex, GCodePlanner gcodeLayer, GCodePathConfig fillConfig, ConfigSettings config) { if(layerIndex >= LastLayerWithChange(config) || extrudersThatHaveBeenPrimed == null) { return; } // print all of the extruder loops that have not already been printed for (int extruderIndex = 0; extruderIndex < config.MaxExtruderCount(); extruderIndex++) { if (!extrudersThatHaveBeenPrimed[extruderIndex]) { // write the loops for this extruder, but don't change to it. We are just filling the prime tower. PrimeOnWipeTower(extruderIndex, 0, gcodeLayer, fillConfig, config); } // clear the history of printer extruders for the next layer extrudersThatHaveBeenPrimed[extruderIndex] = false; } }
public void PrimeOnWipeTower(int extruderIndex, int layerIndex, GCodePlanner gcodeLayer, GCodePathConfig fillConfig, ConfigSettings config) { if (config.WipeTowerSize_um < 1 || extrudersThatHaveBeenPrimed == null || layerIndex > LastLayerWithChange(config) + 1) { return; } //If we changed extruder, print the wipe/prime tower for this nozzle; Polygons fillPolygons = new Polygons(); GenerateWipeTowerInfill(extruderIndex, this.wipeTower, fillPolygons, fillConfig.lineWidth_um, config); gcodeLayer.QueuePolygons(fillPolygons, fillConfig); extrudersThatHaveBeenPrimed[extruderIndex] = true; }
public void QueueNormalSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportNormalConfig) { // normal support Polygons currentSupportOutlines = supportOutlines[layerIndex]; currentSupportOutlines = currentSupportOutlines.Offset(-supportNormalConfig.lineWidth_um / 2); List<Polygons> supportIslands = currentSupportOutlines.ProcessIntoSeparatIslands(); foreach (Polygons islandOutline in supportIslands) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } Polygons islandInfillLines = new Polygons(); // render a grid of support if (config.GenerateSupportPerimeter) { Polygons outlines = Clipper.CleanPolygons(islandOutline, config.ExtrusionWidth_um / 4); gcodeLayer.QueuePolygonsByOptimizer(outlines, supportNormalConfig); } Polygons infillOutline = islandOutline.Offset(-supportNormalConfig.lineWidth_um / 2); if (layerIndex == 0) { // on the first layer print this as solid Infill.GenerateLineInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.ExtrusionWidth_um); } else { switch (config.SupportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, infillOutline, islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; } } gcodeLayer.QueuePolygonsByOptimizer(islandInfillLines, supportNormalConfig); } }
private void WriteSupportPolygons(SliceDataStorage storage, GCodePlanner gcodeLayer, int layerIndex, ConfigSettings config, Polygons supportPolygons, SupportType interfaceLayer) { for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++) { SliceLayer layer = storage.volumes[volumeIndex].layers[layerIndex]; for (int partIndex = 0; partIndex < layer.parts.Count; partIndex++) { supportPolygons = supportPolygons.CreateDifference(layer.parts[partIndex].TotalOutline.Offset(config.supportXYDistance_um)); } } //Contract and expand the support polygons so small sections are removed and the final polygon is smoothed a bit. supportPolygons = supportPolygons.Offset(-config.extrusionWidth_um * 1); supportPolygons = supportPolygons.Offset(config.extrusionWidth_um * 1); List<Polygons> supportIslands = supportPolygons.CreateLayerOutlines(PolygonsHelper.LayerOpperation.EvenOdd); PathOrderOptimizer islandOrderOptimizer = new PathOrderOptimizer(gcode.GetPositionXY()); for (int islandIndex = 0; islandIndex < supportIslands.Count; islandIndex++) { islandOrderOptimizer.AddPolygon(supportIslands[islandIndex][0]); } islandOrderOptimizer.Optimize(); for (int islandIndex = 0; islandIndex < supportIslands.Count; islandIndex++) { Polygons island = supportIslands[islandOrderOptimizer.bestPolygonOrderIndex[islandIndex]]; Polygons supportLines = new Polygons(); if (config.supportLineSpacing_um > 0) { switch (interfaceLayer) { case SupportType.Interface: Infill.GenerateLineInfill(config, island, ref supportLines, config.supportInfillStartingAngle + 90, config.extrusionWidth_um); break; case SupportType.General: switch (config.supportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, island, ref supportLines, config.supportInfillStartingAngle, config.supportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, island, ref supportLines, config.supportInfillStartingAngle, config.supportLineSpacing_um); break; } break; default: throw new NotImplementedException(); } } if (config.avoidCrossingPerimeters) { gcodeLayer.SetOuterPerimetersToAvoidCrossing(island); } switch (interfaceLayer) { case SupportType.Interface: gcodeLayer.WritePolygonsByOptimizer(supportLines, supportInterfaceConfig); break; case SupportType.General: if (config.supportType == ConfigConstants.SUPPORT_TYPE.GRID) { gcodeLayer.WritePolygonsByOptimizer(island, supportNormalConfig); } gcodeLayer.WritePolygonsByOptimizer(supportLines, supportNormalConfig); break; default: throw new NotImplementedException(); } gcodeLayer.SetOuterPerimetersToAvoidCrossing(null); } }
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 QueuePolygonsConsideringSupport(int layerIndex, GCodePlanner gcodeLayer, Polygons polygonsToWrite, GCodePathConfig fillConfig, SupportWriteType supportWriteType) { bool oldLoopValue = fillConfig.closedLoop; if (config.GenerateSupport && layerIndex > 0 && !config.ContinuousSpiralOuterPerimeter) { Polygons supportOutlines = slicingData.support.GetRequiredSupportAreas(layerIndex).Offset(fillConfig.lineWidth_um/2); if (supportWriteType == SupportWriteType.UnsupportedAreas) { if (supportOutlines.Count > 0) { // don't write the bottoms that are sitting on supported areas (they will be written at air gap distance later). Polygons polygonsToWriteAsLines = PolygonsHelper.ConvertToLines(polygonsToWrite); Polygons polygonsNotOnSupport = polygonsToWriteAsLines.CreateLineDifference(supportOutlines); fillConfig.closedLoop = false; gcodeLayer.QueuePolygonsByOptimizer(polygonsNotOnSupport, fillConfig); } else { gcodeLayer.QueuePolygonsByOptimizer(polygonsToWrite, fillConfig); } } else { if (supportOutlines.Count > 0) { if (supportOutlines.Count > 0) { // write the bottoms that are sitting on supported areas. Polygons polygonsToWriteAsLines = PolygonsHelper.ConvertToLines(polygonsToWrite); Polygons polygonsOnSupport = supportOutlines.CreateLineIntersections(polygonsToWriteAsLines); fillConfig.closedLoop = false; gcodeLayer.QueuePolygonsByOptimizer(polygonsOnSupport, fillConfig); } else { gcodeLayer.QueuePolygonsByOptimizer(polygonsToWrite, fillConfig); } } } } else if (supportWriteType == SupportWriteType.UnsupportedAreas) { gcodeLayer.QueuePolygonsByOptimizer(polygonsToWrite, fillConfig); } fillConfig.closedLoop = oldLoopValue; }
//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"); GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); 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.LayerChanged(-3); gcode.SetExtrusion(config.RaftBaseThickness_um, config.FilamentDiameter_um, config.ExtrusionMultiplier); // write the skirt around the raft gcodeLayer.QueuePolygonsByOptimizer(storage.skirt, raftBaseConfig); List <Polygons> raftIslands = storage.raftOutline.ProcessIntoSeparatIslands(); foreach (var raftIsland in raftIslands) { // write the outline of the raft gcodeLayer.QueuePolygonsByOptimizer(raftIsland, raftBaseConfig); 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 gcodeLayer.QueuePolygonsByOptimizer(raftLines, raftBaseConfig); if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } } gcodeLayer.WriteQueuedGCode(config.RaftBaseThickness_um); } if (config.RaftFanSpeedPercent > 0) { gcode.WriteFanCommand(config.RaftFanSpeedPercent); } // raft middle layers { gcode.WriteComment("RAFT MIDDLE"); GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); gcode.SetZ(config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um); gcode.LayerChanged(-2); 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("RAFT SURFACE"); GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.TravelSpeed, config.MinimumTravelToCauseRetraction_um, config.PerimeterStartEndOverlapRatio); gcode.SetZ(config.RaftBaseThickness_um + config.RaftInterfaceThicknes_um + config.RaftSurfaceThickness_um * raftSurfaceIndex); gcode.LayerChanged(-1); 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); } } }
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, ref 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, ref 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, ref raftLines, config.raftSurfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle + 90); } else { Infill.GenerateLinePaths(storage.raftOutline, ref raftLines, config.raftSurfaceLineSpacing_um, config.infillExtendIntoPerimeter_um, 90 * raftSurfaceIndex); } gcodeLayer.QueuePolygonsByOptimizer(raftLines, raftSurfaceConfig); gcodeLayer.WriteQueuedGCode(config.raftInterfaceThicknes_um); } } }
public void QueueInterfaceSupportLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportInterfaceConfig) { // interface Polygons currentInterfaceOutlines2 = interfaceLayers[layerIndex].Offset(-config.ExtrusionWidth_um / 2); if (currentInterfaceOutlines2.Count > 0) { List<Polygons> interfaceIslands = currentInterfaceOutlines2.ProcessIntoSeparatIslands(); foreach (Polygons interfaceOutline in interfaceIslands) { // force a retract if changing islands if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } Polygons supportLines = new Polygons(); Infill.GenerateLineInfill(config, interfaceOutline, supportLines, config.InfillStartingAngle + 90, config.ExtrusionWidth_um); gcodeLayer.QueuePolygonsByOptimizer(supportLines, supportInterfaceConfig); } } }
public void QueueAirGappedBottomLayer(ConfigSettings config, GCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportNormalConfig) { // normal support Polygons currentAirGappedBottoms = airGappedBottomOutlines[layerIndex]; currentAirGappedBottoms = currentAirGappedBottoms.Offset(-config.extrusionWidth_um / 2); List<Polygons> supportIslands = currentAirGappedBottoms.ProcessIntoSeparatIslands(); foreach (Polygons islandOutline in supportIslands) { Polygons islandInfillLines = new Polygons(); // render a grid of support gcodeLayer.QueuePolygonsByOptimizer(islandOutline, supportNormalConfig); Polygons infillOutline = islandOutline.Offset(-config.extrusionWidth_um / 2); switch (config.supportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, infillOutline, ref islandInfillLines, config.supportInfillStartingAngle, config.supportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, infillOutline, ref islandInfillLines, config.supportInfillStartingAngle, config.supportLineSpacing_um); break; } gcodeLayer.QueuePolygonsByOptimizer(islandInfillLines, supportNormalConfig); } }