public static void GenerateInsets(SliceLayerPart part, int offset, int insetCount) { part.AvoidCrossingBoundery = part.TotalOutline.Offset(-offset); if (insetCount == 0) { // if we have no insets defined still create one part.Insets.Add(part.TotalOutline); } else // generate the insets { for (int i = 0; i < insetCount; i++) { part.Insets.Add(new Polygons()); part.Insets[i] = part.TotalOutline.Offset(-offset * i - offset / 2); double minimumDistanceToCreateNewPosition = 10; part.Insets[i] = Clipper.CleanPolygons(part.Insets[i], minimumDistanceToCreateNewPosition); if (part.Insets[i].Count < 1) { part.Insets.RemoveAt(part.Insets.Count - 1); break; } } } }
public static void dumpLayerparts(SliceDataStorage storage, string filename) { StreamWriter streamToWriteTo = new StreamWriter(filename); streamToWriteTo.Write("<!DOCTYPE html><html><body>"); Point3 modelSize = storage.modelSize; Point3 modelMin = storage.modelMin; for (int volumeIdx = 0; volumeIdx < storage.volumes.Count; volumeIdx++) { for (int layerNr = 0; layerNr < storage.volumes[volumeIdx].layers.Count; layerNr++) { streamToWriteTo.Write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n"); SliceLayer layer = storage.volumes[volumeIdx].layers[layerNr]; for (int i = 0; i < layer.parts.Count; i++) { SliceLayerPart part = layer.parts[i]; for (int j = 0; j < part.outline.Count; j++) { streamToWriteTo.Write("<polygon points=\""); for (int k = 0; k < part.outline[j].Count; k++) { streamToWriteTo.Write("{0},{1} ".FormatWith((float)(part.outline[j][k].X - modelMin.x) / modelSize.x * 500, (float)(part.outline[j][k].Y - modelMin.y) / modelSize.y * 500)); } if (j == 0) { streamToWriteTo.Write("\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n"); } else { streamToWriteTo.Write("\" style=\"fill:red; stroke:black;stroke-width:1\" />\n"); } } } streamToWriteTo.Write("</svg>\n"); } } streamToWriteTo.Write("</body></html>"); streamToWriteTo.Close(); }
private static Polygons AddAllOutlines(SliceLayer layerToAdd, SliceLayerPart partToUseAsBounds, Polygons polysToAddTo, ref bool makeInfillSolid, ConfigSettings config) { Polygons polysToIntersect = new Polygons(); for (int partIndex = 0; partIndex < layerToAdd.parts.Count; partIndex++) { var partToConsider = layerToAdd.parts[partIndex]; if (config.smallProtrusionProportion > 0) { // If the part under consideration intersects the part from the current layer and // the area of that intersection is less than smallProtrusionProportion % of the part under consideration solidify the infill var intersection = partToUseAsBounds.TotalOutline.CreateIntersection(partToConsider.TotalOutline); if (intersection.Count > 0) // They do intersect { if (intersection.TotalArea() < partToConsider.TotalOutline.TotalArea() * config.smallProtrusionProportion) { makeInfillSolid = true; return(polysToAddTo); } } } if (partToUseAsBounds.BoundingBox.Hit(partToConsider.BoundingBox)) { polysToIntersect = polysToIntersect.CreateUnion( layerToAdd.parts[partIndex].Insets[partToConsider.Insets.Count - 1]); polysToIntersect = Clipper.CleanPolygons(polysToIntersect, cleanDistance_um); } } polysToAddTo = polysToAddTo.CreateIntersection(polysToIntersect); return(polysToAddTo); }
private void CalculateInfillData(SliceDataStorage storage, int volumeIndex, int layerIndex, SliceLayerPart part, ref Polygons fillPolygons, ref Polygons bridgePolygons) { // generate infill the bottom layers including bridging foreach (Polygons outline in part.SolidBottomOutlines.CreateLayerOutlines(PolygonsHelper.LayerOpperation.EvenOdd)) { if (layerIndex > 0) { double bridgeAngle; if (Bridge.BridgeAngle(outline, storage.volumes[volumeIndex].layers[layerIndex - 1], out bridgeAngle)) { Infill.GenerateLinePaths(outline, ref bridgePolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, bridgeAngle); } else { Infill.GenerateLinePaths(outline, ref fillPolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } } else { Infill.GenerateLinePaths(outline, ref fillPolygons, config.firstLayerExtrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } } // generate infill for the top layers foreach (Polygons outline in part.SolidTopOutlines.CreateLayerOutlines(PolygonsHelper.LayerOpperation.EvenOdd)) { Infill.GenerateLinePaths(outline, ref fillPolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } // generate infill intermediate layers foreach (Polygons outline in part.SolidInfillOutlines.CreateLayerOutlines(PolygonsHelper.LayerOpperation.EvenOdd)) { if (true) // use the old infill method { Infill.GenerateLinePaths(outline, ref fillPolygons, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle + 90 * (layerIndex % 2)); } else // use the new concentric infill (not tested enough yet) have to handle some bad casses better { double oldInfillPercent = config.infillPercent; config.infillPercent = 100; Infill.GenerateConcentricInfill(config, outline, ref fillPolygons); config.infillPercent = oldInfillPercent; } } double fillAngle = config.infillStartingAngle; // generate the infill for this part on this layer if (config.infillPercent > 0) { switch (config.infillType) { case ConfigConstants.INFILL_TYPE.LINES: if ((layerIndex & 1) == 1) { fillAngle += 90; } Infill.GenerateLineInfill(config, part.InfillOutlines, ref fillPolygons, fillAngle); break; case ConfigConstants.INFILL_TYPE.GRID: Infill.GenerateGridInfill(config, part.InfillOutlines, ref fillPolygons, fillAngle); break; case ConfigConstants.INFILL_TYPE.TRIANGLES: Infill.GenerateTriangleInfill(config, part.InfillOutlines, ref fillPolygons, fillAngle); break; case ConfigConstants.INFILL_TYPE.HEXAGON: Infill.GenerateHexagonInfill(config, part.InfillOutlines, ref fillPolygons, fillAngle, layerIndex); break; case ConfigConstants.INFILL_TYPE.CONCENTRIC: Infill.GenerateConcentricInfill(config, part.InfillOutlines, ref fillPolygons); break; default: throw new NotImplementedException(); } } }
public static void generateSparse(int layerIndex, SliceVolumeStorage storage, int extrusionWidth, int downSkinCount, int upSkinCount) { SliceLayer layer = storage.layers[layerIndex]; for (int partNr = 0; partNr < layer.parts.Count; partNr++) { SliceLayerPart part = layer.parts[partNr]; Polygons sparse = part.insets[part.insets.Count - 1].Offset(-extrusionWidth / 2); Polygons downskin = sparse; Polygons upskin = sparse; if ((int)(layerIndex - downSkinCount) >= 0) { SliceLayer layer2 = storage.layers[layerIndex - downSkinCount]; for (int partNr2 = 0; partNr2 < layer2.parts.Count; partNr2++) { if (part.boundaryBox.hit(layer2.parts[partNr2].boundaryBox)) { if (layer2.parts[partNr2].insets.Count > 1) { downskin = downskin.CreateDifference(layer2.parts[partNr2].insets[layer2.parts[partNr2].insets.Count - 2]); } else { downskin = downskin.CreateDifference(layer2.parts[partNr2].insets[layer2.parts[partNr2].insets.Count - 1]); } } } } if ((int)(layerIndex + upSkinCount) < (int)storage.layers.Count) { SliceLayer layer2 = storage.layers[layerIndex + upSkinCount]; for (int partNr2 = 0; partNr2 < layer2.parts.Count; partNr2++) { if (part.boundaryBox.hit(layer2.parts[partNr2].boundaryBox)) { if (layer2.parts[partNr2].insets.Count > 1) { upskin = upskin.CreateDifference(layer2.parts[partNr2].insets[layer2.parts[partNr2].insets.Count - 2]); } else { upskin = upskin.CreateDifference(layer2.parts[partNr2].insets[layer2.parts[partNr2].insets.Count - 1]); } } } } Polygons result = upskin.CreateUnion(downskin); double minAreaSize = 3.0;//(2 * M_PI * ((double)(config.extrusionWidth) / 1000.0) * ((double)(config.extrusionWidth) / 1000.0)) * 3; for (int i = 0; i < result.Count; i++) { double area = Math.Abs(result[i].Area()) / 1000.0 / 1000.0; if (area < minAreaSize) /* Only create an up/down skin if the area is large enough. So you do not create tiny blobs of "trying to fill" */ { result.RemoveAt(i); i -= 1; } } part.sparseOutline = sparse.CreateDifference(result); } }
private static Polygons RemoveAdditionalOutlinesForPart(SliceLayer layerToSubtract, SliceLayerPart partToUseAsBounds, Polygons polygonsToSubtractFrom) { for (int partIndex = 0; partIndex < layerToSubtract.parts.Count; partIndex++) { if (partToUseAsBounds.BoundingBox.Hit(layerToSubtract.parts[partIndex].BoundingBox)) { polygonsToSubtractFrom = polygonsToSubtractFrom.CreateDifference(layerToSubtract.parts[partIndex].Insets[layerToSubtract.parts[partIndex].Insets.Count - 1]); polygonsToSubtractFrom = Clipper.CleanPolygons(polygonsToSubtractFrom, cleanDistance_um); } } return polygonsToSubtractFrom; }
private static Polygons AddAllOutlines(SliceLayer layerToAdd, SliceLayerPart partToUseAsBounds, Polygons polysToAddTo, ref bool makeInfillSolid, ConfigSettings config) { Polygons polysToIntersect = new Polygons(); for (int partIndex = 0; partIndex < layerToAdd.parts.Count; partIndex++) { var partToConsider = layerToAdd.parts[partIndex]; if (config.smallProtrusionProportion > 0) { // If the part under consideration intersects the part from the current layer and // the area of that intersection is less than smallProtrusionProportion % of the part under consideration solidify the infill var intersection = partToUseAsBounds.TotalOutline.CreateIntersection(partToConsider.TotalOutline); if (intersection.Count > 0) // They do intersect { if (intersection.TotalArea() < partToConsider.TotalOutline.TotalArea() * config.smallProtrusionProportion) { makeInfillSolid = true; return polysToAddTo; } } } if (partToUseAsBounds.BoundingBox.Hit(partToConsider.BoundingBox)) { polysToIntersect = polysToIntersect.CreateUnion( layerToAdd.parts[partIndex].Insets[partToConsider.Insets.Count - 1]); polysToIntersect = Clipper.CleanPolygons(polysToIntersect, cleanDistance_um); } } polysToAddTo = polysToAddTo.CreateIntersection(polysToIntersect); return polysToAddTo; }
//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 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); }
public static void GenerateTopAndBottom(int layerIndex, SliceVolumeStorage storage, int extrusionWidth, int downLayerCount, int upLayerCount) { SliceLayer layer = storage.layers[layerIndex]; for (int partIndex = 0; partIndex < layer.parts.Count; partIndex++) { SliceLayerPart part = layer.parts[partIndex]; Polygons insetWithOffset = part.Insets[part.Insets.Count - 1].Offset(-extrusionWidth / 2); Polygons infillOutlines = new Polygons(insetWithOffset); // calculate the bottom outlines if (downLayerCount > 0) { Polygons bottomOutlines = new Polygons(insetWithOffset); if (layerIndex - 1 >= 0) { bottomOutlines = RemoveAdditionalOutlinesForPart(storage.layers[layerIndex - 1], part, bottomOutlines); RemoveSmallAreas(extrusionWidth, bottomOutlines); } infillOutlines = infillOutlines.CreateDifference(bottomOutlines); infillOutlines = Clipper.CleanPolygons(infillOutlines, cleanDistance_um); part.SolidBottomOutlines = bottomOutlines; } // calculate the top outlines if (upLayerCount > 0) { Polygons topOutlines = new Polygons(insetWithOffset); topOutlines = topOutlines.CreateDifference(part.SolidBottomOutlines); topOutlines = Clipper.CleanPolygons(topOutlines, cleanDistance_um); if (part.Insets.Count > 1) { // Add thin wall filling by taking the area between the insets. Polygons thinWalls = part.Insets[0].Offset(-extrusionWidth / 2).CreateDifference(part.Insets[1].Offset(extrusionWidth / 2)); topOutlines.AddAll(thinWalls); } if (layerIndex + 1 < storage.layers.Count) { topOutlines = RemoveAdditionalOutlinesForPart(storage.layers[layerIndex + 1], part, topOutlines); RemoveSmallAreas(extrusionWidth, topOutlines); } infillOutlines = infillOutlines.CreateDifference(topOutlines); infillOutlines = Clipper.CleanPolygons(infillOutlines, cleanDistance_um); part.SolidTopOutlines = topOutlines; } // calculate the solid infill outlines if (upLayerCount > 1 || downLayerCount > 1) { Polygons solidInfillOutlines = new Polygons(insetWithOffset); solidInfillOutlines = solidInfillOutlines.CreateDifference(part.SolidBottomOutlines); solidInfillOutlines = Clipper.CleanPolygons(solidInfillOutlines, cleanDistance_um); solidInfillOutlines = solidInfillOutlines.CreateDifference(part.SolidTopOutlines); solidInfillOutlines = Clipper.CleanPolygons(solidInfillOutlines, cleanDistance_um); int upEnd = layerIndex + upLayerCount + 1; if (upEnd <= storage.layers.Count && layerIndex - downLayerCount >= 0) { Polygons totalPartsToRemove = new Polygons(insetWithOffset); int upStart = layerIndex + 2; for (int layerToTest = upStart; layerToTest < upEnd; layerToTest++) { totalPartsToRemove = AddAllOutlines(storage.layers[layerToTest], part, totalPartsToRemove); totalPartsToRemove = Clipper.CleanPolygons(totalPartsToRemove, cleanDistance_um); } int downStart = layerIndex - 1; int downEnd = layerIndex - downLayerCount; for (int layerToTest = downStart; layerToTest >= downEnd; layerToTest--) { totalPartsToRemove = AddAllOutlines(storage.layers[layerToTest], part, totalPartsToRemove); totalPartsToRemove = Clipper.CleanPolygons(totalPartsToRemove, cleanDistance_um); } solidInfillOutlines = solidInfillOutlines.CreateDifference(totalPartsToRemove); RemoveSmallAreas(extrusionWidth, solidInfillOutlines); solidInfillOutlines = Clipper.CleanPolygons(solidInfillOutlines, cleanDistance_um); } part.SolidInfillOutlines = solidInfillOutlines; infillOutlines = infillOutlines.CreateDifference(solidInfillOutlines); } RemoveSmallAreas(extrusionWidth, infillOutlines); infillOutlines = Clipper.CleanPolygons(infillOutlines, cleanDistance_um); part.InfillOutlines = infillOutlines; } }
private static Polygons RemoveAdditionalOutlinesForPart(SliceLayer layerToSubtract, SliceLayerPart partToUseAsBounds, Polygons polygonsToSubtractFrom) { for (int partIndex = 0; partIndex < layerToSubtract.parts.Count; partIndex++) { if (partToUseAsBounds.BoundingBox.Hit(layerToSubtract.parts[partIndex].BoundingBox)) { polygonsToSubtractFrom = polygonsToSubtractFrom.CreateDifference(layerToSubtract.parts[partIndex].Insets[layerToSubtract.parts[partIndex].Insets.Count - 1]); polygonsToSubtractFrom = Clipper.CleanPolygons(polygonsToSubtractFrom, cleanDistance_um); } } return(polygonsToSubtractFrom); }
public static void GenerateTopAndBottom(int layerIndex, SliceVolumeStorage storage, int extrusionWidth, ConfigSettings config) { var downLayerCount = config.numberOfBottomLayers; var upLayerCount = config.numberOfTopLayers; SliceLayer layer = storage.layers[layerIndex]; for (int partIndex = 0; partIndex < layer.parts.Count; partIndex++) { SliceLayerPart part = layer.parts[partIndex]; Polygons insetWithOffset = part.Insets[part.Insets.Count - 1].Offset(-extrusionWidth / 2); Polygons infillOutlines = new Polygons(insetWithOffset); // calculate the bottom outlines if (downLayerCount > 0) { Polygons bottomOutlines = new Polygons(insetWithOffset); if (layerIndex - 1 >= 0) { bottomOutlines = RemoveAdditionalOutlinesForPart(storage.layers[layerIndex - 1], part, bottomOutlines); RemoveSmallAreas(extrusionWidth, bottomOutlines); } infillOutlines = infillOutlines.CreateDifference(bottomOutlines); infillOutlines = Clipper.CleanPolygons(infillOutlines, cleanDistance_um); part.SolidBottomOutlines = bottomOutlines; } // calculate the top outlines if (upLayerCount > 0) { Polygons topOutlines = new Polygons(insetWithOffset); topOutlines = topOutlines.CreateDifference(part.SolidBottomOutlines); topOutlines = Clipper.CleanPolygons(topOutlines, cleanDistance_um); if (part.Insets.Count > 1) { // Add thin wall filling by taking the area between the insets. Polygons thinWalls = part.Insets[0].Offset(-extrusionWidth / 2).CreateDifference(part.Insets[1].Offset(extrusionWidth / 2)); topOutlines.AddAll(thinWalls); } if (layerIndex + 1 < storage.layers.Count) { topOutlines = RemoveAdditionalOutlinesForPart(storage.layers[layerIndex + 1], part, topOutlines); RemoveSmallAreas(extrusionWidth, topOutlines); } infillOutlines = infillOutlines.CreateDifference(topOutlines); infillOutlines = Clipper.CleanPolygons(infillOutlines, cleanDistance_um); part.SolidTopOutlines = topOutlines; } // calculate the solid infill outlines if (upLayerCount > 1 || downLayerCount > 1) { var solidInfillOutlines = new Polygons(insetWithOffset); solidInfillOutlines = solidInfillOutlines.CreateDifference(part.SolidBottomOutlines); solidInfillOutlines = Clipper.CleanPolygons(solidInfillOutlines, cleanDistance_um); solidInfillOutlines = solidInfillOutlines.CreateDifference(part.SolidTopOutlines); solidInfillOutlines = Clipper.CleanPolygons(solidInfillOutlines, cleanDistance_um); var upStart = layerIndex + 2; var upEnd = layerIndex + upLayerCount + 1; var downStart = layerIndex - 1; var downEnd = layerIndex - downLayerCount; if (upEnd <= storage.layers.Count && downEnd >= 0) { var makeInfillSolid = false; var totalPartsToRemove = new Polygons(insetWithOffset); for (var layerToTest = upStart; layerToTest < upEnd; layerToTest++) { totalPartsToRemove = AddAllOutlines(storage.layers[layerToTest], part, totalPartsToRemove, ref makeInfillSolid, config); totalPartsToRemove = Clipper.CleanPolygons(totalPartsToRemove, cleanDistance_um); if (makeInfillSolid) { break; } } for (var layerToTest = downStart; layerToTest >= downEnd; layerToTest--) { totalPartsToRemove = AddAllOutlines(storage.layers[layerToTest], part, totalPartsToRemove, ref makeInfillSolid, config); totalPartsToRemove = Clipper.CleanPolygons(totalPartsToRemove, cleanDistance_um); if (makeInfillSolid) { break; } } if (!makeInfillSolid) { solidInfillOutlines = solidInfillOutlines.CreateDifference(totalPartsToRemove); RemoveSmallAreas(extrusionWidth, solidInfillOutlines); } solidInfillOutlines = Clipper.CleanPolygons(solidInfillOutlines, cleanDistance_um); } part.SolidInfillOutlines = solidInfillOutlines; infillOutlines = infillOutlines.CreateDifference(solidInfillOutlines); Polygons totalInfillOutlines = null; double totalInfillArea = 0.0; if (config.infillSolidProportion > 0) { totalInfillOutlines = infillOutlines.CreateUnion(solidInfillOutlines); totalInfillArea = totalInfillOutlines.TotalArea(); } if (config.infillSolidProportion > 0) { var solidInfillArea = solidInfillOutlines.TotalArea(); if (solidInfillArea > totalInfillArea * config.infillSolidProportion) { solidInfillOutlines = solidInfillOutlines.CreateUnion(infillOutlines); infillOutlines = new Polygons(); part.SolidInfillOutlines = solidInfillOutlines; } var solidTopOutlinesArea = part.SolidTopOutlines.TotalArea(); if (totalInfillArea < solidTopOutlinesArea * config.infillSolidProportion / 2) { var totalSolidTop = totalInfillOutlines.CreateUnion(part.SolidTopOutlines); part.SolidTopOutlines = totalSolidTop; part.SolidInfillOutlines = new Polygons(); infillOutlines = part.InfillOutlines = new Polygons(); } var solidBottomOutlinesArea = part.SolidBottomOutlines.TotalArea(); if (totalInfillArea < solidBottomOutlinesArea * config.infillSolidProportion / 2) { var totalSolidBottom = totalInfillOutlines.CreateUnion(part.SolidBottomOutlines); part.SolidBottomOutlines = totalSolidBottom; part.SolidInfillOutlines = new Polygons(); infillOutlines = part.InfillOutlines = new Polygons(); } } if (config.minInfillArea_mm2 > 0) { var infillArea = infillOutlines.TotalArea() / 1e6; // convert from um2 to mm2 if (infillArea < config.minInfillArea_mm2) { solidInfillOutlines = solidInfillOutlines.CreateUnion(infillOutlines); infillOutlines = new Polygons(); part.SolidInfillOutlines = solidInfillOutlines; } } } RemoveSmallAreas(extrusionWidth, infillOutlines); infillOutlines = Clipper.CleanPolygons(infillOutlines, cleanDistance_um); part.InfillOutlines = infillOutlines; } }
private void CalculateInfillData(SliceDataStorage storage, int volumeIndex, int layerIndex, int extrusionWidth_um, SliceLayerPart part, ref Polygons fillPolygons, ref Polygons bridgePolygons) { // generate infill for outline including bridging foreach (Polygons outline in part.skinOutline.SplitIntoParts()) { double partFillAngle = config.infillStartingAngle; if ((layerIndex & 1) == 1) { partFillAngle += 90; } if (layerIndex > 0) { double bridgeAngle; if (Bridge.BridgeAngle(outline, storage.volumes[volumeIndex].layers[layerIndex - 1], out bridgeAngle)) { Infill.GenerateLinePaths(outline, ref bridgePolygons, extrusionWidth_um, config.infillExtendIntoPerimeter_um, bridgeAngle); } else { Infill.GenerateLinePaths(outline, ref fillPolygons, extrusionWidth_um, config.infillExtendIntoPerimeter_um, partFillAngle); } } else { Infill.GenerateLinePaths(outline, ref fillPolygons, extrusionWidth_um, config.infillExtendIntoPerimeter_um, partFillAngle); } } double fillAngle = config.infillStartingAngle; // generate the infill for this part on this layer if (config.infillPercent > 0) { switch (config.infillType) { case ConfigConstants.INFILL_TYPE.LINES: if ((layerIndex & 1) == 1) { fillAngle += 90; } Infill.GenerateLineInfill(config, part.sparseOutline, ref fillPolygons, extrusionWidth_um, fillAngle); break; case ConfigConstants.INFILL_TYPE.GRID: Infill.GenerateGridInfill(config, part.sparseOutline, ref fillPolygons, extrusionWidth_um, fillAngle); break; case ConfigConstants.INFILL_TYPE.TRIANGLES: Infill.GenerateTriangleInfill(config, part.sparseOutline, ref fillPolygons, extrusionWidth_um, fillAngle); break; case ConfigConstants.INFILL_TYPE.HEXAGON: Infill.GenerateHexagonInfill(config, part.sparseOutline, ref fillPolygons, extrusionWidth_um, fillAngle, layerIndex); break; case ConfigConstants.INFILL_TYPE.CONCENTRIC: Infill.generateConcentricInfill(config, part.sparseOutline, ref fillPolygons, extrusionWidth_um, fillAngle); break; default: throw new NotImplementedException(); } } }
private static Polygons AddAllOutlines(SliceLayer layerToAdd, SliceLayerPart partToUseAsBounds, Polygons polysToAddTo) { Polygons polysToIntersect = new Polygons(); for (int partIndex = 0; partIndex < layerToAdd.parts.Count; partIndex++) { if (partToUseAsBounds.BoundingBox.Hit(layerToAdd.parts[partIndex].BoundingBox)) { polysToIntersect = polysToIntersect.CreateUnion(layerToAdd.parts[partIndex].Insets[layerToAdd.parts[partIndex].Insets.Count - 1]); polysToIntersect = Clipper.CleanPolygons(polysToIntersect, cleanDistance_um); } } polysToAddTo = polysToAddTo.CreateIntersection(polysToIntersect); return polysToAddTo; }