public NewSupport(ConfigSettings config, List<ExtruderLayers> Extruders, double grabDistanceMm) { long supportWidth_um = (long)(config.extrusionWidth_um * (100-config.supportPercent) / 100); this.grabDistanceMm = grabDistanceMm; // create starting support outlines allPartOutlines = CalculateAllPartOutlines(config, Extruders); allPotentialSupportOutlines = FindAllPotentialSupportOutlines(allPartOutlines, supportWidth_um); allRequiredSupportOutlines = RemoveSelfSupportedSections(allPotentialSupportOutlines, supportWidth_um); if (!config.generateInternalSupport) { allRequiredSupportOutlines = RemoveSupportFromInternalSpaces(allRequiredSupportOutlines, allPartOutlines); } easyGrabDistanceOutlines = ExpandToEasyGrabDistance(allRequiredSupportOutlines, (int)(grabDistanceMm * 1000)); //pushedUpTopOutlines = PushUpTops(easyGrabDistanceOutlines, numLayers, config); interfaceLayers = CreateInterfaceLayers(easyGrabDistanceOutlines, config.supportInterfaceLayers); interfaceLayers = ClipToXyDistance(interfaceLayers, allPartOutlines, config); supportOutlines = AccumulateDownPolygons(config, easyGrabDistanceOutlines, allPartOutlines); supportOutlines = ClipToXyDistance(supportOutlines, allPartOutlines, config); // remove the interface layers from the normal support layers supportOutlines = CalculateDifferencePerLayer(supportOutlines, interfaceLayers); airGappedBottomOutlines = CreateAirGappedBottomLayers(supportOutlines, allPartOutlines); // remove the airGappedBottomOutlines layers from the normal support layers supportOutlines = CalculateDifferencePerLayer(supportOutlines, airGappedBottomOutlines); }
public void GenerateRaftOutlines(int extraDistanceAroundPart_um, ConfigSettings config) { LayerDataStorage storage = this; for (int extruderIndex = 0; extruderIndex < storage.Extruders.Count; extruderIndex++) { if (config.continuousSpiralOuterPerimeter && extruderIndex > 0) { continue; } if (storage.Extruders[extruderIndex].Layers.Count < 1) { continue; } SliceLayer layer = storage.Extruders[extruderIndex].Layers[0]; // let's find the first layer that has something in it for the raft rather than a zero layer if (layer.Islands.Count == 0 && storage.Extruders[extruderIndex].Layers.Count > 2) layer = storage.Extruders[extruderIndex].Layers[1]; for (int partIndex = 0; partIndex < layer.Islands.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } storage.raftOutline = storage.raftOutline.CreateUnion(layer.Islands[partIndex].IslandOutline.Offset(extraDistanceAroundPart_um)); } } storage.raftOutline = storage.raftOutline.CreateUnion(storage.wipeTower.Offset(extraDistanceAroundPart_um)); if (storage.support != null) { storage.raftOutline = storage.raftOutline.CreateUnion(storage.support.GetBedOutlines().Offset(extraDistanceAroundPart_um)); } }
public static void GenerateConcentricInfill(ConfigSettings config, Polygons partOutline, Polygons fillPolygons, long extrusionWidthOverride_um = 0) { if (extrusionWidthOverride_um == 0) { extrusionWidthOverride_um = config.ExtrusionWidth_um; } Polygons outlineCopy = new Polygons(partOutline); foreach (Polygon outline in outlineCopy) { if (outline.Count > 0) { outline.Add(outline[0]); } } int linespacing_um = (int)(extrusionWidthOverride_um / (config.InfillPercent / 100)); while (outlineCopy.Count > 0) { for (int outlineIndex = 0; outlineIndex < outlineCopy.Count; outlineIndex++) { Polygon r = outlineCopy[outlineIndex]; fillPolygons.Add(r); } outlineCopy = outlineCopy.Offset(-linespacing_um); foreach (Polygon outline in outlineCopy) { if (outline.Count > 0) { outline.Add(outline[0]); } } } }
public static void GenerateGridInfill(ConfigSettings config, Polygons partOutline, Polygons fillPolygons, double fillAngle, int linespacing_um = 0) { if (linespacing_um == 0) { if (config.InfillPercent <= 0) { throw new Exception("infillPercent must be greater than 0."); } linespacing_um = (int)(config.ExtrusionWidth_um / (config.InfillPercent / 100) * 2); } Infill.GenerateLinePaths(partOutline, fillPolygons, linespacing_um, config.InfillExtendIntoPerimeter_um, fillAngle); fillAngle += 90; if (fillAngle > 360) { fillAngle -= 360; } Infill.GenerateLinePaths(partOutline, fillPolygons, linespacing_um, config.InfillExtendIntoPerimeter_um, fillAngle); }
public void CreateWipeShield(int totalLayers, ConfigSettings config) { if (config.WipeShieldDistanceFromShapes_um <= 0) { return; } for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { Polygons wipeShield = new Polygons(); for (int extruderIndex = 0; extruderIndex < this.Extruders.Count; extruderIndex++) { for (int islandIndex = 0; islandIndex < this.Extruders[extruderIndex].Layers[layerIndex].Islands.Count; islandIndex++) { wipeShield = wipeShield.CreateUnion(this.Extruders[extruderIndex].Layers[layerIndex].Islands[islandIndex].IslandOutline.Offset(config.WipeShieldDistanceFromShapes_um)); } } this.wipeShield.Add(wipeShield); } for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++) { this.wipeShield[layerIndex] = this.wipeShield[layerIndex].Offset(-1000).Offset(1000); } int offsetAngle = (int)Math.Tan(60.0 * Math.PI / 180) * config.LayerThickness_um;//Allow for a 60deg angle in the wipeShield. for (int layerIndex = 1; layerIndex < totalLayers; layerIndex++) { this.wipeShield[layerIndex] = this.wipeShield[layerIndex].CreateUnion(this.wipeShield[layerIndex - 1].Offset(-offsetAngle)); } for (int layerIndex = totalLayers - 1; layerIndex > 0; layerIndex--) { this.wipeShield[layerIndex - 1] = this.wipeShield[layerIndex - 1].CreateUnion(this.wipeShield[layerIndex].Offset(-offsetAngle)); } }
private static List<Polygons> PushUpTops(List<Polygons> inputPolys, ConfigSettings config) { int numLayers = inputPolys.Count; return inputPolys; int layersFor2Mm = 2000 / config.layerThickness_um; List<Polygons> pushedUpPolys = CreateEmptyPolygons(numLayers); for (int layerIndex = numLayers - 1; layerIndex >= 0; layerIndex--) { for (int layerToAddToIndex = Math.Min(layerIndex + layersFor2Mm, numLayers - 1); layerToAddToIndex >= 0; layerToAddToIndex--) { } Polygons curLayerPolys = inputPolys[layerIndex]; pushedUpPolys[layerIndex] = Clipper.CleanPolygons(curLayerPolys.Offset(config.extrusionWidth_um + config.supportXYDistance_um), cleanDistance_um); } return pushedUpPolys; }
private static List<Polygons> ClipToXyDistance(List<Polygons> inputPolys, List<Polygons> allPartOutlines, ConfigSettings config) { int numLayers = inputPolys.Count; List<Polygons> clippedToXyOutlines = CreateEmptyPolygons(numLayers); for (int layerIndex = numLayers - 2; layerIndex >= 0; layerIndex--) { Polygons curRequiredSupport = inputPolys[layerIndex]; Polygons expandedlayerPolys = allPartOutlines[layerIndex].Offset(config.supportXYDistance_um); Polygons totalSupportThisLayer = curRequiredSupport.CreateDifference(expandedlayerPolys); clippedToXyOutlines[layerIndex] = Clipper.CleanPolygons(totalSupportThisLayer, cleanDistance_um); } return clippedToXyOutlines; }
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 Polygons MakeFuzzy(Polygons polygons, Polygons fuzzyBounds, ConfigSettings config) { if (polygons.Count < 1 || fuzzyBounds?.Count > 0 != true) { return(polygons); } var fuzziness = config.FuzzyThickness_um; var fuzzyFrequency = config.FuzzyFrequency_um; // min 3/4 max 5/4 var minDist = fuzzyFrequency * 3 / 4; var variance = fuzzyFrequency / 2; var fuzzyPolygons = new Polygons(); foreach (var polygon in polygons) { var fuzzyPolygon = new Polygon(); fuzzyPolygons.Add(fuzzyPolygon); // get every line segment including the last for (int pointIndex = 0; pointIndex < polygon.Count; pointIndex++) { var nextIndex = (pointIndex + 1) % polygon.Count; // create the line segment var start = polygon[pointIndex]; var line = new Polygon() { start, polygon[nextIndex] }; var outsideLines = fuzzyBounds.CreateLineDifference(new Polygons() { line }); if (outsideLines.Count == 1 && ((outsideLines[0][0] == line[0] && outsideLines[0][1] == line[1]) || (outsideLines[0][0] == line[1] && outsideLines[0][1] == line[0]))) { // it is not in the area needing fuzzing, add it directly fuzzyPolygon.Add(line[0]); fuzzyPolygon.Add(line[1]); } else { Polygon SortPoints(Polygon poly) { if ((poly[0] - start).LengthSquared() > (poly[1] - start).LengthSquared()) { var hold = poly[0]; poly[0] = poly[1]; poly[1] = hold; } return(poly); } var allLines = new List <(Polygon poly, bool outside)>(); foreach (var outside in outsideLines) { allLines.Add((SortPoints(outside), true)); } var insideLines = fuzzyBounds.CreateLineIntersections(new Polygons() { line }); foreach (var inside in insideLines) { allLines.Add((SortPoints(inside), false)); } // sort the segments along the line direction allLines.Sort((a, b) => { var distToA = (a.poly[0] - start).LengthSquared(); var distToB = (b.poly[0] - start).LengthSquared(); return(distToA.CompareTo(distToB)); }); // iterate over all the sorted segments foreach (var(poly, outside) in allLines) { if (outside) { // just add it fuzzyPolygon.Add(poly[0]); fuzzyPolygon.Add(poly[1]); } else { // if it does not need fuzzing add it to the output // else fuzz the segments and add all the fuzzed pieces // generate points in between p0 and p1 var dist_left_over = (minDist / 4) + rand.Next() % (minDist / 4); // the distance to be traversed on the line before making the first new point var p0 = poly[0]; var p1 = poly[1]; var p0p1 = p1 - p0; var p0p1_size = p0p1.Length(); var p0pa_dist = dist_left_over; if (p0pa_dist >= p0p1_size) { fuzzyPolygon.Add(p1 - (p0p1 / 2)); } for (; p0pa_dist < p0p1_size; p0pa_dist += minDist + rand.Next() % variance) { var r = rand.Next() % (fuzziness * 2) - fuzziness; var perp_to_p0p1 = p0p1.GetPerpendicularLeftXY(); var fuzz = perp_to_p0p1.Normal(r); fuzzyPolygon.Add(p0 + p0p1.Normal(p0pa_dist) + fuzz); } } } } } } return(fuzzyPolygons); }
public void GenerateFillConsideringBridging(Polygons bottomFillIsland, ref Polygons bottomFillLines, ConfigSettings config, string debugName = "") { double bridgeAngle = 0; if (this.BridgeAngle(bottomFillIsland, out bridgeAngle)) { // TODO: Make this code handle very complex pathing between different sizes or layouts of support under the island to fill. Infill.GenerateLinePaths(bottomFillIsland, ref bottomFillLines, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, bridgeAngle); } else { Infill.GenerateLinePaths(bottomFillIsland, ref bottomFillLines, config.extrusionWidth_um, config.infillExtendIntoPerimeter_um, config.infillStartingAngle); } }
public static int ProcessArgs(string[] args, ConfigSettings config, fffProcessor processor) { for (int argn = 0; argn < args.Length; argn++) { string str = args[argn]; if (str[0] == '-') { for (int stringIndex = 1; stringIndex < str.Length; stringIndex++) { switch (str[stringIndex]) { case 'h': print_usage(); return(0); case 'v': LogOutput.verbose_level++; break; case 'o': argn++; if (!processor.SetTargetFile(args[argn])) { LogOutput.LogError("Failed to open {0} for output.\n".FormatWith(args[argn])); return(1); } break; case 'c': { // Read a config file from the given path argn++; if (!config.ReadSettings(args[argn])) { LogOutput.LogError("Failed to read config '{0}'\n".FormatWith(args[argn])); } // process any matrix and mesh requested by config file List <string> commands = new List <string>(); foreach (string command in SplitCommandLine.DoSplit(config.AdditionalArgsToProcess)) { commands.Add(command); } string[] subArgs = commands.ToArray(); ProcessArgs(subArgs, config, processor); } break; case 'd': config.DumpSettings("settings.ini"); break; case 's': { argn++; int equalsPos = args[argn].IndexOf('='); if (equalsPos != -1) { string key = args[argn].Substring(0, equalsPos); string value = args[argn].Substring(equalsPos + 1); if (key.Length > 1) { if (!config.SetSetting(key, value)) { LogOutput.LogError("Setting not found: {0} {1}\n".FormatWith(key, value)); } } } } break; case 'm': argn++; string[] matrixValues = args[argn].Split(','); var loadedMatrix = Matrix4X4.Identity; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { string valueString = matrixValues[i * 4 + j]; double value; if (double.TryParse(valueString, out value)) { loadedMatrix[i, j] = value; } } } config.ModelMatrix = loadedMatrix; break; default: throw new NotImplementedException("Unknown option: {0}\n".FormatWith(str)); //LogOutput.logError("Unknown option: {0}\n".FormatWith(str)); //break; } } } else { processor.LoadStlFile(args[argn]); } } return(1); }
public void GenerateFillConsideringBridging(Polygons bottomFillIsland, Polygons bottomFillLines, ConfigSettings config, Polygons bridgePolygons, string debugName = "") { double bridgeAngle = 0; if (bridgePolygons != null && this.BridgeAngle(bottomFillIsland, out bridgeAngle)) { // TODO: Make this code handle very complex pathing between different sizes or layouts of support under the island to fill. Infill.GenerateLinePaths(bottomFillIsland, bridgePolygons, config.ExtrusionWidth_um, config.InfillExtendIntoPerimeter_um, bridgeAngle); } else { Infill.GenerateLinePaths(bottomFillIsland, bottomFillLines, config.ExtrusionWidth_um, config.InfillExtendIntoPerimeter_um, config.InfillStartingAngle); } }
public static void RemoveExtruderIntersections(List <ExtruderLayers> extruders, ConfigSettings config) { var start = extruders.Count - 1; if (config.BooleanOperations.Contains("F")) { start--; } // Go trough all the extruders, and remove the previous extruders outlines from our own outline, so we never have overlapped areas. for (int extruderIndex = start; extruderIndex >= 0; extruderIndex--) { for (int otherExtruderIndex = extruderIndex - 1; otherExtruderIndex >= 0; otherExtruderIndex--) { for (int layerIndex = 0; layerIndex < extruders[extruderIndex].Layers.Count; layerIndex++) { SliceLayer layerToRemoveFrom = extruders[extruderIndex].Layers[layerIndex]; SliceLayer layerToRemove = extruders[otherExtruderIndex].Layers[layerIndex]; layerToRemoveFrom.AllOutlines = layerToRemoveFrom.AllOutlines.CreateDifference(layerToRemove.AllOutlines); } } } }
private static Polygons GetSkirtBounds(ConfigSettings config, LayerDataStorage storage, bool externalOnly, long distance_um, long extrusionWidth_um, int brimCount) { bool hasWipeTower = storage.WipeLayer(0).PolygonLength() > 0; var skirtPolygons = new Polygons(); if (config.EnableRaft) { skirtPolygons = skirtPolygons.CreateUnion(storage.raftOutline); } else { var allOutlines = hasWipeTower ? new Polygons(storage.WipeLayer(0).Offset(-extrusionWidth_um / 2)) : new Polygons(); if (storage.WipeShield.Count > 0 && storage.WipeShield[0].Count > 0) { allOutlines = allOutlines.CreateUnion(storage.WipeShield[0].Offset(-extrusionWidth_um / 2)); } // Loop over every extruder for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { // Only process the first extruder on spiral vase or // skip extruders that have empty layers if (config.ContinuousSpiralOuterPerimeter) { SliceLayer layer0 = storage.Extruders[extrudeIndex].Layers[0]; if (layer0.Islands.Count > 0) { allOutlines.AddAll(layer0.Islands[0]?.IslandOutline); } } else { // Add the layers outline to allOutlines SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; foreach (var island in layer.Islands) { if (island.IslandOutline?.Count > 0) { allOutlines.Add(island.IslandOutline[0]); } } } } if (brimCount > 0) { Polygons brimIslandOutlines = new Polygons(); // Grow each island by the current brim distance // Union the island brims brimIslandOutlines = brimIslandOutlines.CreateUnion(allOutlines); if (storage.Support != null) { brimIslandOutlines = brimIslandOutlines.CreateUnion(storage.Support.GetBedOutlines()); } Polygons brimLoops = new Polygons(); for (int brimIndex = 0; brimIndex < brimCount; brimIndex++) { // Extend the polygons to account for the brim (ensures convex hull takes this data into account) brimLoops.AddAll(brimIslandOutlines.Offset(extrusionWidth_um * brimIndex + extrusionWidth_um / 2)); } storage.Brims.AddAll(brimLoops); // and extend the bounds of the skirt polygons skirtPolygons = skirtPolygons.CreateUnion(brimIslandOutlines.Offset(extrusionWidth_um * brimCount)); } skirtPolygons = skirtPolygons.CreateUnion(allOutlines); if (storage.Support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.Support.GetBedOutlines()); } } return(skirtPolygons); }
public static void generateSkirt(SliceDataStorage storage, int distance, int extrusionWidth_um, int numberOfLoops, int minLength, int initialLayerHeight, ConfigSettings config) { bool externalOnly = (distance > 0); for (int skirtLoop = 0; skirtLoop < numberOfLoops; skirtLoop++) { int offsetDistance = distance + extrusionWidth_um * skirtLoop + extrusionWidth_um / 2; Polygons skirtPolygons = new Polygons(storage.wipeTower.Offset(offsetDistance)); for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++) { if (config.continuousSpiralOuterPerimeter && volumeIndex > 0) { continue; } if (storage.volumes[volumeIndex].layers.Count < 1) { continue; } SliceLayer layer = storage.volumes[volumeIndex].layers[0]; for (int partIndex = 0; partIndex < layer.parts.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } if (externalOnly) { Polygons p = new Polygons(); p.Add(layer.parts[partIndex].TotalOutline[0]); //p.Add(IntPointHelper.CreateConvexHull(layer.parts[partIndex].outline[0])); skirtPolygons = skirtPolygons.CreateUnion(p.Offset(offsetDistance)); } else { skirtPolygons = skirtPolygons.CreateUnion(layer.parts[partIndex].TotalOutline.Offset(offsetDistance)); } } } SupportPolyGenerator supportGenerator = new SupportPolyGenerator(storage.support, initialLayerHeight); skirtPolygons = skirtPolygons.CreateUnion(supportGenerator.supportPolygons.Offset(offsetDistance)); //Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area" for (int n = 0; n < skirtPolygons.Count; n++) { double area = skirtPolygons[n].Area(); if (area < 0 && area > -extrusionWidth_um * extrusionWidth_um * 100) { skirtPolygons.RemoveAt(n--); } } storage.skirt.AddAll(skirtPolygons); int lenght = (int)storage.skirt.PolygonLength(); if (skirtLoop + 1 >= numberOfLoops && lenght > 0 && lenght < minLength) { // add more loops for as long as we have not extruded enough length numberOfLoops++; } } }
public bool PrimeOnWipeTower(int layerIndex, LayerGCodePlanner layerGcodePlanner, PathFinder pathFinder, GCodePathConfig fillConfig, ConfigSettings config, bool airGapped) { if (!HaveWipeTower(config, layerIndex) || (layerIndex == 0 && !config.EnableRaft)) { return(false); } if (airGapped) { // don't print the wipe tower with air gap height layerGcodePlanner.CurrentZ -= config.SupportAirGap_um; } // If we changed extruder, print the wipe/prime tower for this nozzle; var fillPolygons = new Polygons(); GenerateWipeTowerInfill(primesThisLayer, this.WipeLayer(layerIndex), fillPolygons, fillConfig.LineWidth_um, config); if (fillPolygons.Count > 0) { // move over to the wipe tower with the layer planner in place layerGcodePlanner.QueueTravel(WipeCenter_um, pathFinder); // print the wipe tower with no planning layerGcodePlanner.QueuePolygons(fillPolygons, null, fillConfig); if (airGapped) { // don't print the wipe tower with air gap height layerGcodePlanner.CurrentZ += config.SupportAirGap_um; } primesThisLayer++; return(true); } return(false); }
private int MaxPrimingLoops(ConfigSettings config) { return(Support != null ? config.ExtruderCount * 2 - 2 : config.ExtruderCount - 1); }
public bool EnsureWipeTowerIsSolid(int layerIndex, PathFinder pathFinder, LayerGCodePlanner layerGcodePlanner, GCodePathConfig fillConfig, ConfigSettings config) { if (layerIndex >= LastLayerWithChange(config)) { return(false); } // TODO: if layer index == 0 do all the loops from the outside-in, in order (no lines should be in the wipe tower) if (layerIndex == 0 && !config.EnableRaft) { CheckNoExtruderPrimed(config); long insetPerLoop = fillConfig.LineWidth_um; int maxPrimingLoops = MaxPrimingLoops(config); Polygons outlineForExtruder = this.WipeLayer(layerIndex); var fillPolygons = new Polygons(); while (outlineForExtruder.Count > 0) { for (int polygonIndex = 0; polygonIndex < outlineForExtruder.Count; polygonIndex++) { Polygon newInset = outlineForExtruder[polygonIndex]; newInset.Add(newInset[0]); // add in the last move so it is a solid polygon fillPolygons.Add(newInset); } outlineForExtruder = outlineForExtruder.Offset(-insetPerLoop); } // set the path planner to avoid islands if (this.HaveWipeTower(config, layerIndex)) { layerGcodePlanner.QueueTravel(WipeCenter_um, pathFinder); } // turn off the planner for the wipe tower layerGcodePlanner.QueuePolygons(fillPolygons, null, fillConfig); } else { bool allowPartialInfill = false; // check if there was a change, if not partial fill the wipe tower if (primesThisLayer == 0 && allowPartialInfill) { var outlinePolygons = new Polygons(); for (int i = 0; i < config.NumberOfPerimeters; i++) { var insets = this.WipeLayer(layerIndex).Offset(i * -fillConfig.LineWidth_um); foreach (var inset in insets) { outlinePolygons.Add(inset); // Add the first point back onto the polygon outlinePolygons.Last().Add(outlinePolygons.Last()[0]); } } layerGcodePlanner.QueuePolygons(outlinePolygons, null, fillConfig); // print sparse infill on the wipe tower var fillPolygons = new Polygons(); Infill.GenerateTriangleInfill( config, this.WipeLayer(layerIndex).Offset(config.NumberOfPerimeters * -fillConfig.LineWidth_um), fillPolygons, config.InfillStartingAngle); layerGcodePlanner.QueuePolygonsByOptimizer(fillPolygons, null, fillConfig, layerIndex); } else { // print all of the extruder loops that have not already been printed int maxPrimingLoops = MaxPrimingLoops(config); for (int primeLoop = primesThisLayer; primeLoop < maxPrimingLoops; primeLoop++) { // write the loops for this extruder, but don't change to it. We are just filling the prime tower. PrimeOnWipeTower(layerIndex, layerGcodePlanner, pathFinder, fillConfig, config, false); } } // clear the history of printer extruders for the next layer primesThisLayer = 0; } return(true); }
public static void Generate(ConfigSettings config, Polygons in_outline, Polygons result_lines, bool zigZag, int layerIndex) { // generate infill based on the gyroid equation: sin_x * cos_y + sin_y * cos_z + sin_z * cos_x = 0 // kudos to the author of the Slic3r implementation equation code, the equation code here is based on that Polygons outline = in_outline.Offset(config.ExtrusionWidth_um / 2); var aabb = outline.GetBounds(); var accelerator = new Pathfinding.PathingData(outline, config.ExtrusionWidth_um * 3, true); var linespacing_um = (int)(config.ExtrusionWidth_um / (config.InfillPercent / 100)); var z = layerIndex * config.LayerThickness_um; int pitch = (int)(linespacing_um * 2.41); // this produces similar density to the "line" infill pattern int num_steps = 4; int step = pitch / num_steps; while (step > 500 && num_steps < 16) { num_steps *= 2; step = pitch / num_steps; } pitch = step * num_steps; // recalculate to avoid precision errors double z_rads = 2 * Math.PI * z / pitch; double cos_z = Math.Cos(z_rads); double sin_z = Math.Sin(z_rads); var odd_line_coords = new List <double>(); var even_line_coords = new List <double>(); var result = new Polygons(); var chains = new Polygon[] { new Polygon(), new Polygon() }; // [start_points[], end_points[]] var connected_to = new List <int>[] { new List <int>(), new List <int>() }; // [chain_indices[], chain_indices[]] var line_numbers = new List <int>(); // which row/column line a chain is part of if (Math.Abs(sin_z) <= Math.Abs(cos_z)) { // "vertical" lines double phase_offset = ((cos_z < 0) ? Math.PI : 0) + Math.PI; for (long y = 0; y < pitch; y += step) { double y_rads = 2 * Math.PI * y / pitch; double a = cos_z; double b = Math.Sin(y_rads + phase_offset); double odd_c = sin_z * Math.Cos(y_rads + phase_offset); double even_c = sin_z * Math.Cos(y_rads + phase_offset + Math.PI); double h = Math.Sqrt(a * a + b * b); double odd_x_rads = ((h != 0) ? Math.Asin(odd_c / h) + Math.Asin(b / h) : 0) - Math.PI / 2; double even_x_rads = ((h != 0) ? Math.Asin(even_c / h) + Math.Asin(b / h) : 0) - Math.PI / 2; odd_line_coords.Add(odd_x_rads / Math.PI * pitch); even_line_coords.Add(even_x_rads / Math.PI * pitch); } int num_coords = odd_line_coords.Count; int num_columns = 0; for (long x = (long)((Math.Floor(aabb.minX / (double)pitch) - 2.25) * pitch); x <= aabb.maxX + pitch / 2; x += pitch / 2) { bool is_first_point = true; IntPoint last = default(IntPoint); bool last_inside = false; int chain_end_index = 0; var chain_end = new IntPoint[2]; for (long y = (long)((Math.Floor(aabb.minY / (double)pitch) - 1) * pitch); y <= aabb.maxY + pitch; y += pitch) { for (int i = 0; i < num_coords; ++i) { var current = new IntPoint(x + (((num_columns & 1) == 1) ? odd_line_coords[i] : even_line_coords[i]) / 2 + pitch, y + (long)(i * step)); bool current_inside = accelerator.PointIsInside(current) == QTPolygonsExtensions.InsideState.Inside; if (!is_first_point) { if (last_inside && current_inside) { // line doesn't hit the boundary, add the whole line result.AddLine(last, current); } else if (last_inside != current_inside) { // line hits the boundary, add the part that's inside the boundary var line = new Polygons(); line.AddLine(last, current); line = outline.CreateLineIntersections(line); if (line.Count > 0) { // some of the line is inside the boundary result.AddLine(line[0][0], line[0][1]); var end = line[0][(line[0][0] != last && line[0][0] != current) ? 0 : 1]; var lineNumber = num_columns; if (zigZag) { chain_end[chain_end_index] = end; if (++chain_end_index == 2) { chains[0].Add(chain_end[0]); chains[1].Add(chain_end[1]); chain_end_index = 0; connected_to[0].Add(int.MaxValue); connected_to[1].Add(int.MaxValue); line_numbers.Add(lineNumber); } } } else { // none of the line is inside the boundary so the point that's actually on the boundary // is the chain end var end = last_inside ? last : current; var lineNumber = num_columns; if (zigZag) { chain_end[chain_end_index] = end; if (++chain_end_index == 2) { chains[0].Add(chain_end[0]); chains[1].Add(chain_end[1]); chain_end_index = 0; connected_to[0].Add(int.MaxValue); connected_to[1].Add(int.MaxValue); line_numbers.Add(lineNumber); } } } } } last = current; last_inside = current_inside; is_first_point = false; } } ++num_columns; } } else { // "horizontal" lines double phase_offset = (sin_z < 0) ? Math.PI : 0; for (long x = 0; x < pitch; x += step) { double x_rads = 2 * Math.PI * x / pitch; double a = sin_z; double b = Math.Cos(x_rads + phase_offset); double odd_c = cos_z * Math.Sin(x_rads + phase_offset + Math.PI); double even_c = cos_z * Math.Sin(x_rads + phase_offset); double h = Math.Sqrt(a * a + b * b); double odd_y_rads = ((h != 0) ? Math.Asin(odd_c / h) + Math.Asin(b / h) : 0) + Math.PI / 2; double even_y_rads = ((h != 0) ? Math.Asin(even_c / h) + Math.Asin(b / h) : 0) + Math.PI / 2; odd_line_coords.Add(odd_y_rads / Math.PI * pitch); even_line_coords.Add(even_y_rads / Math.PI * pitch); } int num_coords = odd_line_coords.Count; int num_rows = 0; for (long y = (long)((Math.Floor(aabb.minY / (double)pitch) - 1) * pitch); y <= aabb.maxY + pitch / 2; y += pitch / 2) { bool is_first_point = true; IntPoint last = default(IntPoint); bool last_inside = false; int chain_end_index = 0; var chain_end = new IntPoint[2]; for (long x = (long)((Math.Floor(aabb.minX / (double)pitch) - 1) * pitch); x <= aabb.maxX + pitch; x += pitch) { for (int i = 0; i < num_coords; ++i) { var current = new IntPoint(x + (long)(i * step), y + (((num_rows & 1) == 1) ? odd_line_coords[i] : even_line_coords[i]) / 2); bool current_inside = accelerator.PointIsInside(current) == QTPolygonsExtensions.InsideState.Inside; if (!is_first_point) { if (last_inside && current_inside) { // line doesn't hit the boundary, add the whole line result.AddLine(last, current); } else if (last_inside != current_inside) { // line hits the boundary, add the part that's inside the boundary var line = new Polygons(); line.AddLine(last, current); line = outline.CreateLineIntersections(line); if (line.Count > 0) { // some of the line is inside the boundary result.AddLine(line[0][0], line[0][1]); var end = line[0][(line[0][0] != last && line[0][0] != current) ? 0 : 1]; var lineNumber = num_rows; if (zigZag) { chain_end[chain_end_index] = end; if (++chain_end_index == 2) { chains[0].Add(chain_end[0]); chains[1].Add(chain_end[1]); chain_end_index = 0; connected_to[0].Add(int.MaxValue); connected_to[1].Add(int.MaxValue); line_numbers.Add(lineNumber); } } } else { // none of the line is inside the boundary so the point that's actually on the boundary // is the chain end var end = (last_inside) ? last : current; var lineNumber = num_rows; if (zigZag) { chain_end[chain_end_index] = end; if (++chain_end_index == 2) { chains[0].Add(chain_end[0]); chains[1].Add(chain_end[1]); chain_end_index = 0; connected_to[0].Add(int.MaxValue); connected_to[1].Add(int.MaxValue); line_numbers.Add(lineNumber); } } } } } last = current; last_inside = current_inside; is_first_point = false; } } ++num_rows; } } if (zigZag && chains[0].Count > 0) { // zig-zag connecting consists of joining alternate chain ends to make a chain of chains // the basic algorithm is that we follow the infill area boundary and as we progress we are either drawing a connector or not // whenever we come across the end of a chain we toggle the connector drawing state // things are made more complicated by the fact that we want to avoid generating loops and so we need to keep track // of the identity of the first chain in a connected sequence int chain_ends_remaining = chains[0].Count * 2; foreach (var outline_poly in outline) { var connector_points = new Polygon(); // the points that make up a connector line // we need to remember the first chain processed and the path to it from the first outline point // so that later we can possibly connect to it from the last chain processed int first_chain_chain_index = int.MaxValue; var path_to_first_chain = new Polygon(); bool drawing = false; // true when a connector line is being (potentially) created // keep track of the chain+point that a connector line started at int connector_start_chain_index = int.MaxValue; int connector_start_point_index = int.MaxValue; IntPoint cur_point = default(IntPoint); // current point of interest - either an outline point or a chain end // go round all of the region's outline and find the chain ends that meet it // quit the loop early if we have seen all the chain ends and are not currently drawing a connector for (int outline_point_index = 0; (chain_ends_remaining > 0 || drawing) && outline_point_index < outline_poly.Count; ++outline_point_index) { var op0 = outline_poly[outline_point_index]; var op1 = outline_poly[(outline_point_index + 1) % outline_poly.Count()]; var points_on_outline_chain_index = new List <int>(); var points_on_outline_point_index = new List <int>(); // collect the chain ends that meet this segment of the outline for (int chain_index = 0; chain_index < chains[0].Count; ++chain_index) { for (int point_index = 0; point_index < 2; ++point_index) { // don't include chain ends that are close to the segment but are beyond the segment ends int beyond = 0; if (GetDist2FromLineSegment(op0, chains[point_index][chain_index], op1, ref beyond) < 10 && beyond != 0) { points_on_outline_point_index.Add(point_index); points_on_outline_chain_index.Add(chain_index); } } } if (outline_point_index == 0 || (op0 - cur_point).Length() > 100) { // this is either the first outline point or it is another outline point that is not too close to cur_point if (first_chain_chain_index == int.MaxValue) { // include the outline point in the path to the first chain path_to_first_chain.Add(op0); } cur_point = op0; if (drawing) { // include the start point of this outline segment in the connector connector_points.Add(op0); } } // iterate through each of the chain ends that meet the current outline segment while (points_on_outline_chain_index.Count > 0) { // find the nearest chain end to the current point int nearest_point_index = 0; var nearest_point_dist_squared = double.MaxValue; for (int pi = 0; pi < points_on_outline_chain_index.Count; ++pi) { var first = chains[points_on_outline_point_index[pi]][points_on_outline_chain_index[pi]]; var dist_squared = (first - cur_point).LengthSquared(); if (dist_squared < nearest_point_dist_squared) { nearest_point_dist_squared = dist_squared; nearest_point_index = pi; } } int point_index = points_on_outline_point_index[nearest_point_index]; int chain_index = points_on_outline_chain_index[nearest_point_index]; // make the chain end the current point and add it to the connector line cur_point = chains[point_index][chain_index]; if (drawing && connector_points.Count > 0 && (cur_point - connector_points.Last()).Length() < 100) { // this chain end will be too close to the last connector point so throw away the last connector point connector_points.RemoveAt(connector_points.Count - 1); } connector_points.Add(cur_point); if (first_chain_chain_index == int.MaxValue) { // this is the first chain to be processed, remember it first_chain_chain_index = chain_index; path_to_first_chain.Add(cur_point); } if (drawing) { // add the connector line segments but only if // 1 - the start/end points are not the opposite ends of the same chain // 2 - the other end of the current chain is not connected to the chain the connector line is coming from if (chain_index != connector_start_chain_index && connected_to[(point_index + 1) % 2][chain_index] != connector_start_chain_index) { for (int pi = 1; pi < connector_points.Count; ++pi) { result.AddLine(connector_points[pi - 1], connector_points[pi]); } drawing = false; connector_points.Clear(); // remember the connection connected_to[point_index][chain_index] = connector_start_chain_index; connected_to[connector_start_point_index][connector_start_chain_index] = chain_index; } else { // start a new connector from the current location connector_points.Clear(); connector_points.Add(cur_point); // remember the chain+point that the connector started from connector_start_chain_index = chain_index; connector_start_point_index = point_index; } } else { // we have just jumped a gap so now we want to start drawing again drawing = true; // if this connector is the first to be created or we are not connecting chains from the same row/column, // remember the chain+point that this connector is starting from if (connector_start_chain_index == int.MaxValue || line_numbers[chain_index] != line_numbers[connector_start_chain_index]) { connector_start_chain_index = chain_index; connector_start_point_index = point_index; } } // done with this chain end if (points_on_outline_chain_index.Count > 0) { points_on_outline_chain_index.RemoveAt(Math.Min(points_on_outline_chain_index.Count - 1, points_on_outline_chain_index[0] + nearest_point_index)); } if (points_on_outline_chain_index.Count > 0) { points_on_outline_chain_index.RemoveAt(Math.Min(points_on_outline_chain_index.Count - 1, points_on_outline_chain_index[0] + nearest_point_index)); } // decrement total amount of work to do --chain_ends_remaining; } } // we have now visited all the points in the outline, if a connector was (potentially) being drawn // check whether the first chain is already connected to the last chain and, if not, draw the // connector between if (drawing && first_chain_chain_index != int.MaxValue && first_chain_chain_index != connector_start_chain_index && connected_to[0][first_chain_chain_index] != connector_start_chain_index && connected_to[1][first_chain_chain_index] != connector_start_chain_index) { // output the connector line segments from the last chain to the first point in the outline connector_points.Add(outline_poly[0]); for (int pi = 1; pi < connector_points.Count; ++pi) { result.AddLine(connector_points[pi - 1], connector_points[pi]); } // output the connector line segments from the first point in the outline to the first chain for (int pi = 1; pi < path_to_first_chain.Count; ++pi) { result.AddLine(path_to_first_chain[pi - 1], path_to_first_chain[pi]); } } if (chain_ends_remaining < 1) { break; } } } result_lines.AddRange(result); }
private static List <Polygons> AccumulateDownPolygons(ConfigSettings config, List <Polygons> inputPolys, List <Polygons> allPartOutlines) { int numLayers = inputPolys.Count; long nozzleSize = config.ExtrusionWidth_um; long areaToTryAndBe = 20 * 20 * nozzleSize * nozzleSize; // 10 x 10 mm approximately (assuming .5 nozzle) List <Polygons> allDownOutlines = CreateEmptyPolygons(numLayers); for (int layerIndex = numLayers - 2; layerIndex >= 0; layerIndex--) { Polygons aboveRequiredSupport = inputPolys[layerIndex + 1]; // get all the polygons above us Polygons accumulatedAbove = allDownOutlines[layerIndex + 1].CreateUnion(aboveRequiredSupport); // experimental and not working well enough yet if (config.MinimizeSupportColumns) { // reduce the amount of support material used for (int i = accumulatedAbove.Count - 1; i >= 0; i--) { Polygon polygon = accumulatedAbove[i]; double polyArea = polygon.Area(); if (polyArea > areaToTryAndBe) { Polygons offsetPolygons = new Polygons() { polygon }.Offset(-config.ExtrusionWidth_um / 2); accumulatedAbove.RemoveAt(i); foreach (Polygon polyToAdd in offsetPolygons) { accumulatedAbove.Insert(i, polyToAdd); } } else if (polyArea < areaToTryAndBe * .9) { Polygons offsetPolygons = new Polygons() { polygon }.Offset(config.ExtrusionWidth_um / 2); accumulatedAbove.RemoveAt(i); foreach (Polygon polyToAdd in offsetPolygons) { accumulatedAbove.Insert(i, polyToAdd); } } } } // add in the support on this level Polygons curRequiredSupport = inputPolys[layerIndex]; Polygons totalSupportThisLayer = accumulatedAbove.CreateUnion(curRequiredSupport); // remove the solid polygons on this level Polygons remainingAbove = totalSupportThisLayer.CreateDifference(allPartOutlines[layerIndex]); allDownOutlines[layerIndex] = Clipper.CleanPolygons(remainingAbove, cleanDistance_um); } return(allDownOutlines); }
public void GenerateInsets(ConfigSettings config, Polygons fuzzyBounds, long extrusionWidth_um, long outerExtrusionWidth_um, int insetCount, bool avoidCrossingPerimeters) { LayerIsland part = this; part.BoundingBox.Calculate(part.IslandOutline); if (avoidCrossingPerimeters) { part.PathFinder = new PathFinder(part.IslandOutline, extrusionWidth_um * 3 / 2, useInsideCache: avoidCrossingPerimeters, name: "inset island"); } if (insetCount == 0) { // if we have no insets defined still create one part.InsetToolPaths.Add(part.IslandOutline); } else // generate the insets { long currentOffset = 0; // Inset 0 will use the outerExtrusionWidth_um, everyone else will use extrusionWidth_um long offsetBy = outerExtrusionWidth_um / 2; for (int i = 0; i < insetCount; i++) { // Increment by half the offset amount currentOffset += offsetBy; Polygons currentInset = part.IslandOutline.Offset(-currentOffset); // make the outer perimeter fuzzy if needed if (i == 0) { currentInset = MakeFuzzy(currentInset, fuzzyBounds, config); } // make sure our polygon data is reasonable if (config.MergeOverlappingLines) { // be aggressive about maintaining small polygons currentInset = Clipper.CleanPolygons(currentInset); } else { // clean the polygon to make it have less jaggies currentInset = Clipper.CleanPolygons(currentInset, minimumDistanceToCreateNewPosition); } // check that we have actual paths if (currentInset.Count > 0) { var run = true; // if we are centering the seam put a point exactly in back if (run && (config.SeamPlacement == SEAM_PLACEMENT.ALWAYS_CENTERED_IN_BACK || config.SeamPlacement == SEAM_PLACEMENT.CENTERED_IN_BACK)) { foreach (var polygon in currentInset) { var count = polygon.Count; if (count > 2) { // if we are going to center the seam in the back make sure there is a vertex to center on that is exactly in back var centeredIndex = polygon.GetCenteredInBackIndex(out IntPoint center); var start = -1; var end = -1; if (polygon[centeredIndex].X <= center.X) { // start should always be left of center start = centeredIndex; // is the next point right of center end = (centeredIndex + 1) % count; if (polygon[end].X < center.X) { // no it is left of center // is the previous point right of center end = (centeredIndex + count - 1) % count; if (polygon[end].X < center.X) { // it is still left of center (so no crossing) continue; // skip placing a seam at this point } } } else if (polygon[centeredIndex].X >= center.X) { // we are to the right of center so this is the end end = centeredIndex; // set start to the left of center start = (centeredIndex + 1) % count; if (polygon[start].X > center.X) { // start is also right of center it need to be left start = (centeredIndex + count - 1) % count; if (polygon[start].X > center.X) { // it is still right of center skip this point continue; } } } // find the y intercept var delta = polygon[end] - polygon[start]; if (delta.X != 0) { var insert = Math.Max(start, end); if (insert == count - 1 && (start == 0 || end == 0)) { insert = count; } var ratio = (center.X - polygon[start].X) / (double)delta.X; polygon.Insert(insert, new IntPoint(center.X, polygon[start].Y + (polygon[end].Y - polygon[start].Y) * ratio)); } } } } part.InsetToolPaths.Add(currentInset); // Increment by the second half currentOffset += offsetBy; } else { // we are done making insets as we have no area left break; } if (i == 0) { // Reset offset amount to half the standard extrusion width offsetBy = extrusionWidth_um / 2; } } } }
private static List <Polygons> ClipToXyDistance(List <Polygons> inputPolys, List <Polygons> allPartOutlines, ConfigSettings config) { int numLayers = inputPolys.Count; List <Polygons> clippedToXyOutlines = CreateEmptyPolygons(numLayers); for (int layerIndex = numLayers - 2; layerIndex >= 0; layerIndex--) { Polygons curRequiredSupport = inputPolys[layerIndex]; Polygons expandedlayerPolys = allPartOutlines[layerIndex].Offset(config.SupportXYDistance_um); Polygons totalSupportThisLayer = curRequiredSupport.CreateDifference(expandedlayerPolys); clippedToXyOutlines[layerIndex] = Clipper.CleanPolygons(totalSupportThisLayer, cleanDistance_um); } return(clippedToXyOutlines); }
int LastLayerWithChange(ConfigSettings config) { int numLayers = Extruders[0].Layers.Count; int firstExtruderWithData = -1; for (int checkLayer = numLayers - 1; checkLayer >= 0; checkLayer--) { for (int extruderToCheck = 0; extruderToCheck < config.MaxExtruderCount(); extruderToCheck++) { if((extruderToCheck < Extruders.Count && Extruders[extruderToCheck].Layers[checkLayer].AllOutlines.Count > 0) || (config.SupportExtruder == extruderToCheck && support != null && support.HasNormalSupport(checkLayer) ) || (config.SupportInterfaceExtruder == extruderToCheck && support != null && support.HasInterfaceSupport(checkLayer)) ) { if(firstExtruderWithData == -1) { firstExtruderWithData = extruderToCheck; } else { if(firstExtruderWithData != extruderToCheck) { return checkLayer; } } } } } return -1; }
public void GenerateTopAndBottoms(ConfigSettings config, int layerIndex, long extrusionWidth_um, long outerPerimeterWidth_um, int downLayerCount, int upLayerCount, long infillExtendIntoPerimeter_um) { var clippingOffset = infillExtendIntoPerimeter_um * 2; ExtruderLayers extruder = this; SliceLayer layer = extruder.Layers[layerIndex]; for (int islandIndex = 0; islandIndex < layer.Islands.Count; islandIndex++) { LayerIsland island = layer.Islands[islandIndex]; if (island.InsetToolPaths.Count == 0) { return; } // this is the entire extrusion width to make sure we are outside of the extrusion line Polygons lastInset = island.InsetToolPaths[island.InsetToolPaths.Count - 1]; Polygons infillRegionPath = lastInset.Offset(-extrusionWidth_um); Polygons sparseInfillPaths = new Polygons(infillRegionPath); // calculate the bottom outlines if (downLayerCount > 0) { Polygons bottomOutlines = new Polygons(infillRegionPath); if (layerIndex - 1 >= 0) { var previousLayer = extruder.Layers[layerIndex - 1]; bottomOutlines = RemoveIslandsFromPolygons(previousLayer.Islands, island.BoundingBox, bottomOutlines); bottomOutlines.RemoveSmallAreas(extrusionWidth_um); } sparseInfillPaths = sparseInfillPaths.CreateDifference(bottomOutlines); sparseInfillPaths = Clipper.CleanPolygons(sparseInfillPaths, cleanDistance_um); island.BottomPaths = bottomOutlines; } // calculate the top outlines if (upLayerCount > 0) { Polygons topOutlines = new Polygons(infillRegionPath); topOutlines = topOutlines.CreateDifference(island.BottomPaths.Offset(clippingOffset)); topOutlines = Clipper.CleanPolygons(topOutlines, cleanDistance_um); if (layerIndex + 1 < extruder.Layers.Count) { // Remove the top layer that is above this one to get only the data that is a top layer on this layer. topOutlines = RemoveIslandsFromPolygons(extruder.Layers[layerIndex + 1].Islands, island.BoundingBox, topOutlines); } topOutlines.RemoveSmallAreas(extrusionWidth_um); sparseInfillPaths = sparseInfillPaths.CreateDifference(topOutlines.Offset(clippingOffset)); sparseInfillPaths = Clipper.CleanPolygons(sparseInfillPaths, cleanDistance_um); island.TopPaths = topOutlines; } if (upLayerCount <= 0 && downLayerCount <= 0) { // Assign infill directly if no top/bottom solid layers island.SparseInfillPaths = sparseInfillPaths; } else { // calculate the solid infill outlines Polygons solidInfillPaths = new Polygons(infillRegionPath); // remove all the top layers solidInfillPaths = solidInfillPaths.CreateDifference(island.BottomPaths.Offset(clippingOffset)); solidInfillPaths = Clipper.CleanPolygons(solidInfillPaths, cleanDistance_um); // remove all the bottom layers solidInfillPaths = solidInfillPaths.CreateDifference(island.TopPaths.Offset(clippingOffset)); solidInfillPaths = Clipper.CleanPolygons(solidInfillPaths, cleanDistance_um); int upEnd = layerIndex + upLayerCount + 1; if (upEnd <= extruder.Layers.Count && layerIndex - downLayerCount >= 0) { // find all the regions that have more top and bottom layers than should be solid (will remain sparse) Polygons regionsThatWillBeSparse = new Polygons(infillRegionPath); int upStart = layerIndex + 2; for (int layerToTest = upStart; layerToTest < upEnd; layerToTest++) { regionsThatWillBeSparse = IntersectWithPolygons(extruder.Layers[layerToTest].Islands, island.BoundingBox, regionsThatWillBeSparse); regionsThatWillBeSparse = Clipper.CleanPolygons(regionsThatWillBeSparse, cleanDistance_um); } // find all the solid infill bottom layers int downStart = Math.Max(0, layerIndex - 1); int downEnd = Math.Max(0, layerIndex - downLayerCount); for (int layerToTest = downStart; layerToTest >= downEnd; layerToTest--) { regionsThatWillBeSparse = IntersectWithPolygons(extruder.Layers[layerToTest].Islands, island.BoundingBox, regionsThatWillBeSparse); regionsThatWillBeSparse = Clipper.CleanPolygons(regionsThatWillBeSparse, cleanDistance_um); } solidInfillPaths = solidInfillPaths.CreateDifference(regionsThatWillBeSparse); solidInfillPaths.RemoveSmallAreas(extrusionWidth_um); solidInfillPaths = Clipper.CleanPolygons(solidInfillPaths, cleanDistance_um); } // remove the solid infill from the sparse infill sparseInfillPaths = sparseInfillPaths.CreateDifference(solidInfillPaths.Offset(clippingOffset)); sparseInfillPaths.RemoveSmallAreas(extrusionWidth_um); sparseInfillPaths = Clipper.CleanPolygons(sparseInfillPaths, cleanDistance_um); island.SparseInfillPaths = sparseInfillPaths; if (config == null || // this is to make our tests test the bridgeOverInfill config.BridgeOverInfill) { // now figure out what part of the solid infill is actually first top layers and switch it to that // we can only have a first top y layer at the bottom of the top layers if (layerIndex == extruder.Layers.Count - upLayerCount) { // all of it is first top layers island.FirstTopPaths = solidInfillPaths; solidInfillPaths = new Polygons(); } else if (layerIndex > 0 && layerIndex < extruder.Layers.Count - upLayerCount) { // Intersect the current solid layer with the previous spars layer // that will be all of the new solid layers that are currently on sparse layer var firstTopPaths = new Polygons(solidInfillPaths); firstTopPaths = IntersectWithSparsePolygons(extruder.Layers[layerIndex - 1].Islands, island.BoundingBox, firstTopPaths); firstTopPaths.RemoveSmallAreas(extrusionWidth_um); firstTopPaths = Clipper.CleanPolygons(firstTopPaths, cleanDistance_um); if (firstTopPaths.Count > 0) { solidInfillPaths = solidInfillPaths.CreateDifference(firstTopPaths.Offset(clippingOffset)); solidInfillPaths.RemoveSmallAreas(extrusionWidth_um); solidInfillPaths = Clipper.CleanPolygons(solidInfillPaths, cleanDistance_um); island.FirstTopPaths = firstTopPaths; } } } island.SolidInfillPaths = solidInfillPaths; } } }
public void GenerateSkirt(int distance, int extrusionWidth_um, int numberOfLoops, int minLength, int initialLayerHeight, ConfigSettings config) { LayerDataStorage storage = this; bool externalOnly = (distance > 0); for (int skirtLoop = 0; skirtLoop < numberOfLoops; skirtLoop++) { int offsetDistance = distance + extrusionWidth_um * skirtLoop + extrusionWidth_um / 2; Polygons skirtPolygons = new Polygons(storage.wipeTower.Offset(offsetDistance)); for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { if (config.continuousSpiralOuterPerimeter && extrudeIndex > 0) { continue; } if (storage.Extruders[extrudeIndex].Layers.Count < 1) { continue; } SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; for (int partIndex = 0; partIndex < layer.Islands.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } if (externalOnly) { Polygons p = new Polygons(); p.Add(layer.Islands[partIndex].IslandOutline[0]); //p.Add(IntPointHelper.CreateConvexHull(layer.parts[partIndex].outline[0])); skirtPolygons = skirtPolygons.CreateUnion(p.Offset(offsetDistance)); } else { skirtPolygons = skirtPolygons.CreateUnion(layer.Islands[partIndex].IslandOutline.Offset(offsetDistance)); } } } if (storage.support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.support.GetBedOutlines().Offset(offsetDistance)); } //Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area" for (int n = 0; n < skirtPolygons.Count; n++) { double area = skirtPolygons[n].Area(); if (area < 0 && area > -extrusionWidth_um * extrusionWidth_um * 100) { skirtPolygons.RemoveAt(n--); } } storage.skirt.AddAll(skirtPolygons); int lenght = (int)storage.skirt.PolygonLength(); if (skirtLoop + 1 >= numberOfLoops && lenght > 0 && lenght < minLength) { // add more loops for as long as we have not extruded enough length numberOfLoops++; } } }
public void CreateRequiredInsets(ConfigSettings config, int outputLayerIndex, int extruderIndex) { if (extruderIndex < this.Extruders.Count) { var startIndex = Math.Max(0, outputLayerIndex - config.NumberOfBottomLayers - 1); // figure out how many layers we need to calculate var endIndex = outputLayerIndex + config.NumberOfTopLayers + 1; var threadExtra = 10; endIndex = (endIndex / threadExtra) * threadExtra + threadExtra; // now bias more in to help multi threading // and clamp to the number there are to make sure we can do it endIndex = Math.Min(endIndex, this.Extruders[extruderIndex].Layers.Count); // free up the insets from the previous layer if (startIndex > config.NumberOfBottomLayers + 1) { SliceLayer previousLayer = this.Extruders[extruderIndex].Layers[startIndex - 2]; previousLayer.FreeIslandMemory(); } using (new QuickTimer2Report("GenerateInsets")) { for (int layerIndex = startIndex; layerIndex < endIndex; layerIndex++) { SliceLayer layer = this.Extruders[extruderIndex].Layers[layerIndex]; if (layer.Islands.Count > 0 && !layer.CreatedInsets) { layer.CreatedInsets = true; int insetCount = config.GetNumberOfPerimeters(); if (config.ContinuousSpiralOuterPerimeter && (int)layerIndex < config.NumberOfBottomLayers && layerIndex % 2 == 1) { // Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight. insetCount += 1; } Polygons fuzzyBounds = null; if (layerIndex > 0 && FuzzyLayerBounds != null && FuzzyLayerBounds.Count > layerIndex) { fuzzyBounds = FuzzyLayerBounds[layerIndex]; } if (layerIndex == 0) { layer.GenerateInsets(config, fuzzyBounds, config.FirstLayerExtrusionWidth_um, config.FirstLayerExtrusionWidth_um, insetCount); } else { layer.GenerateInsets(config, fuzzyBounds, config.ExtrusionWidth_um, config.OutsideExtrusionWidth_um, insetCount); } } } } using (new QuickTimer2Report("GenerateTopAndBottoms")) { // Only generate bottom and top layers and infill for the first X layers when spiralize is chosen. if (!config.ContinuousSpiralOuterPerimeter || (int)outputLayerIndex < config.NumberOfBottomLayers) { if (outputLayerIndex == 0) { this.Extruders[extruderIndex].GenerateTopAndBottoms(config, outputLayerIndex, config.FirstLayerExtrusionWidth_um, config.FirstLayerExtrusionWidth_um, config.NumberOfBottomLayers, config.NumberOfTopLayers, config.InfillExtendIntoPerimeter_um); } else { this.Extruders[extruderIndex].GenerateTopAndBottoms(config, outputLayerIndex, config.ExtrusionWidth_um, config.OutsideExtrusionWidth_um, config.NumberOfBottomLayers, config.NumberOfTopLayers, config.InfillExtendIntoPerimeter_um); } } } } }
public static void GenerateTriangleInfill(ConfigSettings config, Polygons partOutline, ref Polygons fillPolygons, double fillAngle) { if (config.infillPercent <= 0) { throw new Exception("infillPercent must be gerater than 0."); } int linespacing_um = (int)(config.extrusionWidth_um / (config.infillPercent / 100) * 3); long offset = linespacing_um / 2; Infill.GenerateLinePaths(partOutline, ref fillPolygons, linespacing_um, config.infillExtendIntoPerimeter_um, fillAngle, offset); fillAngle += 60; if (fillAngle > 360) { fillAngle -= 360; } Infill.GenerateLinePaths(partOutline, ref fillPolygons, linespacing_um, config.infillExtendIntoPerimeter_um, fillAngle, offset); fillAngle += 60; if (fillAngle > 360) { fillAngle -= 360; } Infill.GenerateLinePaths(partOutline, ref fillPolygons, linespacing_um, config.infillExtendIntoPerimeter_um, fillAngle, offset); }
public void GenerateSkirt(int distance, int extrusionWidth_um, int numberOfLoops, int brimCount, int minLength, ConfigSettings config) { Polygons islandsToSkirtAround = GetSkirtBounds(config, this, (distance > 0), distance, extrusionWidth_um, brimCount); if (islandsToSkirtAround.Count > 0) { // Find convex hull for the skirt outline Polygons convexHull = new Polygons(new[] { islandsToSkirtAround.CreateConvexHull() }); // Create skirt loops from the ConvexHull for (int skirtLoop = 0; skirtLoop < numberOfLoops; skirtLoop++) { int offsetDistance = distance + extrusionWidth_um * skirtLoop + extrusionWidth_um / 2; this.Skirt.AddAll(convexHull.Offset(offsetDistance)); int length = (int)this.Skirt.PolygonLength(); if (skirtLoop + 1 >= numberOfLoops && length > 0 && length < minLength) { // add more loops for as long as we have not extruded enough length numberOfLoops++; } } } }
public fffProcessor(ConfigSettings config) { this.config = config; fileNumber = 1; maxObjectHeight = 0; }
public static void GenerateRaftOutlines(SliceDataStorage storage, int extraDistanceAroundPart_um, ConfigSettings config) { for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++) { if (config.continuousSpiralOuterPerimeter && volumeIndex > 0) { continue; } if (storage.volumes[volumeIndex].layers.Count < 1) { continue; } SliceLayer layer = storage.volumes[volumeIndex].layers[0]; // let's find the first layer that has something in it for the raft rather than a zero layer if (layer.parts.Count == 0 && storage.volumes[volumeIndex].layers.Count > 2) { layer = storage.volumes[volumeIndex].layers[1]; } for (int partIndex = 0; partIndex < layer.parts.Count; partIndex++) { if (config.continuousSpiralOuterPerimeter && partIndex > 0) { continue; } storage.raftOutline = storage.raftOutline.CreateUnion(layer.parts[partIndex].TotalOutline.Offset(extraDistanceAroundPart_um)); } } SupportPolyGenerator supportGenerator = new SupportPolyGenerator(storage.support, 0); storage.raftOutline = storage.raftOutline.CreateUnion(storage.wipeTower.Offset(extraDistanceAroundPart_um)); storage.raftOutline = storage.raftOutline.CreateUnion(supportGenerator.supportPolygons.Offset(extraDistanceAroundPart_um)); }
private static List<Polygons> CalculateAllPartOutlines(ConfigSettings config, List<ExtruderLayers> Extruders) { int numLayers = Extruders[0].Layers.Count; List<Polygons> allPartOutlines = CreateEmptyPolygons(numLayers); foreach (var extruder in Extruders) { // calculate the combined outlines for everything for (int layerIndex = numLayers - 1; layerIndex >= 0; layerIndex--) { allPartOutlines[layerIndex] = allPartOutlines[layerIndex].CreateUnion(extruder.Layers[layerIndex].AllOutlines); } } return allPartOutlines; }
public static bool ShouldGenerateRaft(ConfigSettings config) { return(config.enableRaft && config.raftBaseThickness_um > 0 && config.raftInterfaceThicknes_um > 0); }
private static List<Polygons> AccumulateDownPolygons(ConfigSettings config, List<Polygons> inputPolys, List<Polygons> allPartOutlines) { int numLayers = inputPolys.Count; long nozzleSize = config.extrusionWidth_um; long areaToTryAndBe = 20 * 20 * nozzleSize * nozzleSize; // 10 x 10 mm approximately (assuming .5 nozzle) List<Polygons> allDownOutlines = CreateEmptyPolygons(numLayers); for (int layerIndex = numLayers - 2; layerIndex >= 0; layerIndex--) { Polygons aboveRequiredSupport = inputPolys[layerIndex + 1]; // get all the polygons above us Polygons accumulatedAbove = allDownOutlines[layerIndex + 1].CreateUnion(aboveRequiredSupport); // experimental and not working well enough yet if (config.minimizeSupportColumns) { // reduce the amount of support material used for (int i = accumulatedAbove.Count - 1; i >= 0; i--) { Polygon polygon = accumulatedAbove[i]; double polyArea = polygon.Area(); if (polyArea > areaToTryAndBe) { Polygons offsetPolygons = new Polygons() { polygon }.Offset(-config.extrusionWidth_um / 2); accumulatedAbove.RemoveAt(i); foreach (Polygon polyToAdd in offsetPolygons) { accumulatedAbove.Insert(i, polyToAdd); } } else if (polyArea < areaToTryAndBe * .9) { Polygons offsetPolygons = new Polygons() { polygon }.Offset(config.extrusionWidth_um / 2); accumulatedAbove.RemoveAt(i); foreach (Polygon polyToAdd in offsetPolygons) { accumulatedAbove.Insert(i, polyToAdd); } } } } // add in the support on this level Polygons curRequiredSupport = inputPolys[layerIndex]; Polygons totalSupportThisLayer = accumulatedAbove.CreateUnion(curRequiredSupport); // remove the solid polygons on this level Polygons remainingAbove = totalSupportThisLayer.CreateDifference(allPartOutlines[layerIndex]); allDownOutlines[layerIndex] = Clipper.CleanPolygons(remainingAbove, cleanDistance_um); } return allDownOutlines; }
public void CreateWipeTower(int totalLayers, ConfigSettings config) { if (config.WipeTowerSize_um < 1 || LastLayerWithChange(config) == -1) { return; } extrudersThatHaveBeenPrimed = new bool[config.MaxExtruderCount()]; Polygon wipeTowerShape = new Polygon(); wipeTowerShape.Add(new IntPoint(this.modelMin.x - 3000, this.modelMax.y + 3000)); wipeTowerShape.Add(new IntPoint(this.modelMin.x - 3000, this.modelMax.y + 3000 + config.WipeTowerSize_um)); wipeTowerShape.Add(new IntPoint(this.modelMin.x - 3000 - config.WipeTowerSize_um, this.modelMax.y + 3000 + config.WipeTowerSize_um)); wipeTowerShape.Add(new IntPoint(this.modelMin.x - 3000 - config.WipeTowerSize_um, this.modelMax.y + 3000)); this.wipeTower.Add(wipeTowerShape); this.wipePoint = new IntPoint(this.modelMin.x - 3000 - config.WipeTowerSize_um / 2, this.modelMax.y + 3000 + config.WipeTowerSize_um / 2); }
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); } }
public void GenerateSkirt(int distance, int extrusionWidth_um, int numberOfLoops, int brimCount, int minLength, int initialLayerHeight, ConfigSettings config) { LayerDataStorage storage = this; bool externalOnly = (distance > 0); List<Polygons> skirtLoops = new List<Polygons>(); Polygons skirtPolygons = GetSkirtBounds(config, storage, externalOnly, distance, extrusionWidth_um, brimCount); // Find convex hull for the skirt outline Polygons convexHull = new Polygons(new[] { skirtPolygons.CreateConvexHull() }); // Create skirt loops from the ConvexHull for (int skirtLoop = 0; skirtLoop < numberOfLoops; skirtLoop++) { int offsetDistance = distance + extrusionWidth_um * skirtLoop + extrusionWidth_um / 2; storage.skirt.AddAll(convexHull.Offset(offsetDistance)); int length = (int)storage.skirt.PolygonLength(); if (skirtLoop + 1 >= numberOfLoops && length > 0 && length < minLength) { // add more loops for as long as we have not extruded enough length numberOfLoops++; } } }
public Slicer(OptimizedVolume ov, ConfigSettings config) { int initialLayerThickness_um = config.firstLayerThickness_um; int layerThickness_um = config.layerThickness_um; ConfigConstants.REPAIR_OUTLINES outlineRepairTypes = config.repairOutlines; modelSize = ov.parentModel.size_um; modelMin = ov.parentModel.minXYZ_um; int heightWithoutFirstLayer = modelSize.z - initialLayerThickness_um - config.bottomClipAmount_um; int countOfNormalThicknessLayers = (int)((heightWithoutFirstLayer / (double)layerThickness_um) + .5); int layerCount = countOfNormalThicknessLayers; if (initialLayerThickness_um > 0) { // we have to add in the first layer (that is a differnt size) layerCount++; } LogOutput.Log(string.Format("Layer count: {0}\n", layerCount)); layers.Capacity = layerCount; for (int layerIndex = 0; layerIndex < layerCount; layerIndex++) { int z; if (layerIndex == 0) { z = initialLayerThickness_um / 2; } else { z = initialLayerThickness_um + layerThickness_um / 2 + layerThickness_um * (layerIndex - 1); } layers.Add(new SlicerLayer(z)); } for (int faceIndex = 0; faceIndex < ov.facesTriangle.Count; faceIndex++) { Point3 p0 = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[0]].position; Point3 p1 = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[1]].position; Point3 p2 = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[2]].position; int minZ = p0.z; int maxZ = p0.z; if (p1.z < minZ) minZ = p1.z; if (p2.z < minZ) minZ = p2.z; if (p1.z > maxZ) maxZ = p1.z; if (p2.z > maxZ) maxZ = p2.z; for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++) { int z = layers[layerIndex].Z; if (z < minZ || z > maxZ) { continue; } SlicerSegment polyCrossingAtThisZ; if (p0.z < z && p1.z >= z && p2.z >= z) { // p1 p2 // -------- // p0 polyCrossingAtThisZ = GetCrossingAtZ(p0, p2, p1, z); } else if (p0.z >= z && p1.z < z && p2.z < z) { // p0 // -------- // p1 p2 polyCrossingAtThisZ = GetCrossingAtZ(p0, p1, p2, z); } else if (p1.z < z && p0.z >= z && p2.z >= z) { // p0 p2 // -------- // p1 polyCrossingAtThisZ = GetCrossingAtZ(p1, p0, p2, z); } else if (p1.z >= z && p0.z < z && p2.z < z) { // p1 // -------- // p0 p2 polyCrossingAtThisZ = GetCrossingAtZ(p1, p2, p0, z); } else if (p2.z < z && p1.z >= z && p0.z >= z) { // p1 p0 // -------- // p2 polyCrossingAtThisZ = GetCrossingAtZ(p2, p1, p0, z); } else if (p2.z >= z && p1.z < z && p0.z < z) { // p2 // -------- // p1 p0 polyCrossingAtThisZ = GetCrossingAtZ(p2, p0, p1, z); } else { //Not all cases create a segment, because a point of a face could create just a dot, and two touching faces // on the slice would create two segments continue; } polyCrossingAtThisZ.hasBeenAddedToPolygon = false; layers[layerIndex].SegmentList.Add(polyCrossingAtThisZ); } } for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++) { layers[layerIndex].MakePolygons(outlineRepairTypes); } }
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 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); Polygons raftLines = new Polygons(); Infill.GenerateLinePaths(storage.raftOutline, ref raftLines, config.raftBaseLineSpacing_um, config.infillExtendIntoPerimeter_um, 0); // write the skirt around the raft gcodeLayer.WritePolygonsByOptimizer(storage.skirt, raftBaseConfig); // write the outline of the raft gcodeLayer.WritePolygonsByOptimizer(storage.raftOutline, raftBaseConfig); // write the inside of the raft base 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 bool QueueNormalSupportLayer(ConfigSettings config, LayerGCodePlanner gcodeLayer, int layerIndex, GCodePathConfig supportNormalConfig) { // normal support Polygons currentSupportOutlines = SparseSupportOutlines[layerIndex]; List <Polygons> supportIslands = currentSupportOutlines.ProcessIntoSeparateIslands(); List <PathFinder> pathFinders = new List <PathFinder>(); List <Polygons> infillOutlines = new List <Polygons>(); for (int i = 0; i < supportIslands.Count; i++) { pathFinders.Add(null); infillOutlines.Add(null); } Agg.Parallel.For(0, supportIslands.Count, (index) => { var infillOffset = -config.ExtrusionWidth_um + config.InfillExtendIntoPerimeter_um; var supportIsland = supportIslands[index]; infillOutlines[index] = supportIsland.Offset(infillOffset); if (config.AvoidCrossingPerimeters) { pathFinders[index] = new PathFinder(infillOutlines[index], -config.ExtrusionWidth_um / 2, useInsideCache: config.AvoidCrossingPerimeters, name: "normal support"); } }); bool outputPaths = false; for (int i = 0; i < supportIslands.Count; i++) { var supportIsland = supportIslands[i]; // force a retract if changing islands if (config.RetractWhenChangingIslands) { gcodeLayer.ForceRetract(); } // make a border if layer 0 if (config.GenerateSupportPerimeter || layerIndex == 0) { if (gcodeLayer.QueuePolygonsByOptimizer(supportIsland.Offset(-config.ExtrusionWidth_um / 2), pathFinders[i], supportNormalConfig, 0)) { outputPaths = true; } } Polygons islandInfillLines = new Polygons(); if (layerIndex == 0) { // on the first layer print this as solid Infill.GenerateLineInfill(config, infillOutlines[i], islandInfillLines, config.SupportInfillStartingAngle, config.ExtrusionWidth_um); } else { switch (config.SupportType) { case ConfigConstants.SUPPORT_TYPE.GRID: Infill.GenerateGridInfill(config, infillOutlines[i], islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; case ConfigConstants.SUPPORT_TYPE.LINES: Infill.GenerateLineInfill(config, infillOutlines[i], islandInfillLines, config.SupportInfillStartingAngle, config.SupportLineSpacing_um); break; } } if (gcodeLayer.QueuePolygonsByOptimizer(islandInfillLines, pathFinders[i], supportNormalConfig, 0)) { outputPaths |= true; } } return(outputPaths); }
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 static int ProcessArgs(string[] args) { if (args.Length == 0) { print_usage(); return(0); } ConfigSettings config = new ConfigSettings(); fffProcessor processor = new fffProcessor(config); LogOutput.Log("\nMatterSlice version {0}\n\n".FormatWith(ConfigConstants.VERSION)); for (int argn = 0; argn < args.Length; argn++) { string str = args[argn]; if (str[0] == '-') { for (int stringIndex = 1; stringIndex < str.Length; stringIndex++) { switch (str[stringIndex]) { case 'h': print_usage(); return(0); case 'v': LogOutput.verbose_level++; break; case 'o': argn++; if (!processor.SetTargetFile(args[argn])) { LogOutput.LogError("Failed to open {0} for output.\n".FormatWith(args[argn])); return(1); } break; case 'c': { // Read a config file from the given path argn++; if (!config.ReadSettings(args[argn])) { LogOutput.LogError("Failed to read config '{0}'\n".FormatWith(args[argn])); } } break; case 'b': argn++; config.BooleanOpperations = args[argn]; break; case 'd': config.DumpSettings("settings.ini"); break; case 's': { argn++; int equalsPos = args[argn].IndexOf('='); if (equalsPos != -1) { string key = args[argn].Substring(0, equalsPos); string value = args[argn].Substring(equalsPos + 1); if (key.Length > 1) { if (!config.SetSetting(key, value)) { LogOutput.LogError("Setting not found: {0} {1}\n".FormatWith(key, value)); } } } } break; case 'm': argn++; throw new NotImplementedException("m"); #if false sscanf(argv[argn], "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf", &config.matrix.m[0][0], &config.matrix.m[0][1], &config.matrix.m[0][2], &config.matrix.m[1][0], &config.matrix.m[1][1], &config.matrix.m[1][2], &config.matrix.m[2][0], &config.matrix.m[2][1], &config.matrix.m[2][2]); #endif //break; default: throw new NotImplementedException("Unknown option: {0}\n".FormatWith(str)); //LogOutput.logError("Unknown option: {0}\n".FormatWith(str)); //break; } } } else { processor.LoadStlFile(args[argn]); } } if (!Canceled) { processor.DoProcessing(); } if (!Canceled) { processor.finalize(); } if (Canceled) { processor.Cancel(); } return(0); }
public void GenerateWipeTowerInfill(int extruderIndex, Polygons partOutline, Polygons outputfillPolygons, long extrusionWidth_um, ConfigSettings config) { Polygons outlineForExtruder = partOutline.Offset(-extrusionWidth_um * extruderIndex); long insetPerLoop = extrusionWidth_um * config.MaxExtruderCount(); while (outlineForExtruder.Count > 0) { for (int polygonIndex = 0; polygonIndex < outlineForExtruder.Count; polygonIndex++) { Polygon newInset = outlineForExtruder[polygonIndex]; newInset.Add(newInset[0]); // add in the last move so it is a solid polygon outputfillPolygons.Add(newInset); } outlineForExtruder = outlineForExtruder.Offset(-insetPerLoop); } outputfillPolygons.Reverse(); }
public void GenerateSupportGrid(OptimizedModel model, ConfigSettings config) { this.generated = false; if (config.supportEndAngle < 0) { return; } this.generated = true; this.gridOffset.X = model.minXYZ_um.x; this.gridOffset.Y = model.minXYZ_um.y; this.gridScale = 200; this.gridWidth = (model.size_um.x / this.gridScale) + 1; this.gridHeight = (model.size_um.y / this.gridScale) + 1; int gridSize = this.gridWidth * this.gridHeight; this.xYGridOfSupportPoints = new List <List <SupportPoint> >(gridSize); for (int i = 0; i < gridSize; i++) { this.xYGridOfSupportPoints.Add(new List <SupportPoint>()); } this.endAngleDegrees = config.supportEndAngle; this.generateInternalSupport = config.generateInternalSupport; this.supportXYDistance_um = config.supportXYDistance_um; this.supportLayerHeight_um = config.layerThickness_um; this.supportZGapLayers = config.supportNumberOfLayersToSkipInZ; this.supportInterfaceLayers = config.supportInterfaceLayers; // This should really be a ray intersection as later code is going to count on it being an even odd list of bottoms and tops. // As it is we are finding the hit on the plane but not checking for good intersection with the triangle. for (int volumeIndex = 0; volumeIndex < model.volumes.Count; volumeIndex++) { OptimizedVolume vol = model.volumes[volumeIndex]; for (int faceIndex = 0; faceIndex < vol.facesTriangle.Count; faceIndex++) { OptimizedFace faceTriangle = vol.facesTriangle[faceIndex]; Point3 v0 = vol.vertices[faceTriangle.vertexIndex[0]].position; Point3 v1 = vol.vertices[faceTriangle.vertexIndex[1]].position; Point3 v2 = vol.vertices[faceTriangle.vertexIndex[2]].position; // get the angle of this polygon double angleFromHorizon; { FPoint3 v0f = new FPoint3(v0); FPoint3 v1f = new FPoint3(v1); FPoint3 v2f = new FPoint3(v2); FPoint3 normal = (v1f - v0f).Cross(v2f - v0f); normal.z = Math.Abs(normal.z); angleFromHorizon = (Math.PI / 2) - FPoint3.CalculateAngle(normal, FPoint3.Up); } v0.x = (int)((v0.x - this.gridOffset.X) / (double)this.gridScale + .5); v0.y = (int)((v0.y - this.gridOffset.Y) / (double)this.gridScale + .5); v1.x = (int)((v1.x - this.gridOffset.X) / (double)this.gridScale + .5); v1.y = (int)((v1.y - this.gridOffset.Y) / (double)this.gridScale + .5); v2.x = (int)((v2.x - this.gridOffset.X) / (double)this.gridScale + .5); v2.y = (int)((v2.y - this.gridOffset.Y) / (double)this.gridScale + .5); if (v0.x > v1.x) { swap(ref v0, ref v1); } if (v1.x > v2.x) { swap(ref v1, ref v2); } if (v0.x > v1.x) { swap(ref v0, ref v1); } for (long x = v0.x; x < v1.x; x++) { long y0 = (long)(v0.y + (v1.y - v0.y) * (x - v0.x) / (double)(v1.x - v0.x) + .5); long y1 = (long)(v0.y + (v2.y - v0.y) * (x - v0.x) / (double)(v2.x - v0.x) + .5); long z0 = (long)(v0.z + (v1.z - v0.z) * (x - v0.x) / (double)(v1.x - v0.x) + .5); long z1 = (long)(v0.z + (v2.z - v0.z) * (x - v0.x) / (double)(v2.x - v0.x) + .5); if (y0 > y1) { swap(ref y0, ref y1); swap(ref z0, ref z1); } for (long y = y0; y < y1; y++) { SupportPoint newSupportPoint = new SupportPoint((int)(z0 + (z1 - z0) * (y - y0) / (double)(y1 - y0) + .5), angleFromHorizon); this.xYGridOfSupportPoints[(int)(x + y * this.gridWidth)].Add(newSupportPoint); } } for (int x = v1.x; x < v2.x; x++) { long y0 = (long)(v1.y + (v2.y - v1.y) * (x - v1.x) / (double)(v2.x - v1.x) + .5); long y1 = (long)(v0.y + (v2.y - v0.y) * (x - v0.x) / (double)(v2.x - v0.x) + .5); long z0 = (long)(v1.z + (v2.z - v1.z) * (x - v1.x) / (double)(v2.x - v1.x) + .5); long z1 = (long)(v0.z + (v2.z - v0.z) * (x - v0.x) / (double)(v2.x - v0.x) + .5); if (y0 > y1) { swap(ref y0, ref y1); swap(ref z0, ref z1); } for (int y = (int)y0; y < y1; y++) { this.xYGridOfSupportPoints[x + y * this.gridWidth].Add(new SupportPoint((int)(z0 + (z1 - z0) * (double)(y - y0) / (double)(y1 - y0) + .5), angleFromHorizon)); } } } } for (int x = 0; x < this.gridWidth; x++) { for (int y = 0; y < this.gridHeight; y++) { int gridIndex = x + y * this.gridWidth; List <SupportPoint> currentList = this.xYGridOfSupportPoints[gridIndex]; currentList.Sort(SortSupportsOnZ); if (currentList.Count > 1) { // now remove duplicates (try to make it a better bottom and top list) for (int i = currentList.Count - 1; i >= 1; i--) { if (currentList[i].z == currentList[i - 1].z) { currentList.RemoveAt(i); } } } } } this.gridOffset.X += this.gridScale / 2; this.gridOffset.Y += this.gridScale / 2; }
private static Polygons GetSkirtBounds(ConfigSettings config, LayerDataStorage storage, bool externalOnly, int distance, int extrusionWidth_um, int brimCount) { bool hasWipeTower = storage.wipeTower.PolygonLength() > 0; Polygons skirtPolygons = hasWipeTower ? new Polygons(storage.wipeTower) : new Polygons(); if (config.EnableRaft) { skirtPolygons = skirtPolygons.CreateUnion(storage.raftOutline); } else { Polygons allOutlines = new Polygons(); // Loop over every extruder for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { // Only process the first extruder on spiral vase or // skip extruders that have empty layers if (config.ContinuousSpiralOuterPerimeter) { SliceLayer layer0 = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer0.Islands[0]?.IslandOutline); break; } // Add the layers outline to allOutlines SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer.AllOutlines); } if (brimCount > 0) { Polygons brimLoops = new Polygons(); // Loop over the requested brimCount creating and unioning a new perimeter for each island for (int brimIndex = 0; brimIndex < brimCount; brimIndex++) { int offsetDistance = extrusionWidth_um * brimIndex + extrusionWidth_um / 2; Polygons unionedIslandOutlines = new Polygons(); // Grow each island by the current brim distance foreach (var island in allOutlines) { var polygons = new Polygons(); polygons.Add(island); // Union the island brims unionedIslandOutlines = unionedIslandOutlines.CreateUnion(polygons.Offset(offsetDistance)); } // Extend the polygons to account for the brim (ensures convex hull takes this data into account) brimLoops.AddAll(unionedIslandOutlines); } // TODO: This is a quick hack, reuse the skirt data to stuff in the brim. Good enough from proof of concept storage.skirt.AddAll(brimLoops); skirtPolygons = skirtPolygons.CreateUnion(brimLoops); } else { skirtPolygons = skirtPolygons.CreateUnion(allOutlines); } if (storage.support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.support.GetBedOutlines()); } } return skirtPolygons; }
public Slicer(OptimizedMesh ov, ConfigSettings config) { int initialLayerThickness_um = config.FirstLayerThickness_um; int layerThickness_um = config.LayerThickness_um; modelSize = ov.containingCollection.size_um; modelMin = ov.containingCollection.minXYZ_um; long heightWithoutFirstLayer = modelSize.z - initialLayerThickness_um - config.BottomClipAmount_um; int countOfNormalThicknessLayers = Math.Max(0, (int)((heightWithoutFirstLayer / (double)layerThickness_um) + .5)); int layerCount = countOfNormalThicknessLayers; if (initialLayerThickness_um > 0) { // we have to add in the first layer (that is a different size) layerCount++; } if (config.outputOnlyFirstLayer) { layerCount = 1; } LogOutput.Log(string.Format("Layer count: {0}\n", layerCount)); layers.Capacity = layerCount; for (int layerIndex = 0; layerIndex < layerCount; layerIndex++) { int z; if (layerIndex == 0) { z = initialLayerThickness_um / 2; } else { z = initialLayerThickness_um + layerThickness_um / 2 + layerThickness_um * (layerIndex - 1); } layers.Add(new MeshProcessingLayer(z)); } for (int faceIndex = 0; faceIndex < ov.facesTriangle.Count; faceIndex++) { Point3 p0 = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[0]].position; Point3 p1 = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[1]].position; Point3 p2 = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[2]].position; long minZ = p0.z; long maxZ = p0.z; if (p1.z < minZ) { minZ = p1.z; } if (p2.z < minZ) { minZ = p2.z; } if (p1.z > maxZ) { maxZ = p1.z; } if (p2.z > maxZ) { maxZ = p2.z; } for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++) { int z = layers[layerIndex].Z; if (z < minZ || z > maxZ) { continue; } SlicePerimeterSegment polyCrossingAtThisZ; if (p0.z < z && p1.z >= z && p2.z >= z) { // p1 p2 // -------- // p0 polyCrossingAtThisZ = GetCrossingAtZ(p0, p2, p1, z); } else if (p0.z >= z && p1.z < z && p2.z < z) { // p0 // -------- // p1 p2 polyCrossingAtThisZ = GetCrossingAtZ(p0, p1, p2, z); } else if (p1.z < z && p0.z >= z && p2.z >= z) { // p0 p2 // -------- // p1 polyCrossingAtThisZ = GetCrossingAtZ(p1, p0, p2, z); } else if (p1.z >= z && p0.z < z && p2.z < z) { // p1 // -------- // p0 p2 polyCrossingAtThisZ = GetCrossingAtZ(p1, p2, p0, z); } else if (p2.z < z && p1.z >= z && p0.z >= z) { // p1 p0 // -------- // p2 polyCrossingAtThisZ = GetCrossingAtZ(p2, p1, p0, z); } else if (p2.z >= z && p1.z < z && p0.z < z) { // p2 // -------- // p1 p0 polyCrossingAtThisZ = GetCrossingAtZ(p2, p0, p1, z); } else { //Not all cases create a segment, because a point of a face could create just a dot, and two touching faces // on the slice would create two segments continue; } polyCrossingAtThisZ.hasBeenAddedToPolygon = false; layers[layerIndex].SegmentList.Add(polyCrossingAtThisZ); } } for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++) { layers[layerIndex].MakePolygons(); } }
public void GenerateSkirt(int distance, int extrusionWidth_um, int numberOfLoops, int brimCount, int minLength, int initialLayerHeight, ConfigSettings config) { LayerDataStorage storage = this; bool externalOnly = (distance > 0); List <Polygons> skirtLoops = new List <Polygons>(); Polygons skirtPolygons = GetSkirtBounds(config, storage, externalOnly, distance, extrusionWidth_um, brimCount); // Find convex hull for the skirt outline Polygons convexHull = new Polygons(new[] { skirtPolygons.CreateConvexHull() }); // Create skirt loops from the ConvexHull for (int skirtLoop = 0; skirtLoop < numberOfLoops; skirtLoop++) { int offsetDistance = distance + extrusionWidth_um * skirtLoop + extrusionWidth_um / 2; storage.skirt.AddAll(convexHull.Offset(offsetDistance)); int length = (int)storage.skirt.PolygonLength(); if (skirtLoop + 1 >= numberOfLoops && length > 0 && length < minLength) { // add more loops for as long as we have not extruded enough length numberOfLoops++; } } }
private static Polygons GetSkirtBounds(ConfigSettings config, LayerDataStorage storage, bool externalOnly, int distance, int extrusionWidth_um, int brimCount) { bool hasWipeTower = storage.wipeTower.PolygonLength() > 0; Polygons skirtPolygons = hasWipeTower ? new Polygons(storage.wipeTower) : new Polygons(); if (config.EnableRaft) { skirtPolygons = skirtPolygons.CreateUnion(storage.raftOutline); } else { Polygons allOutlines = new Polygons(); // Loop over every extruder for (int extrudeIndex = 0; extrudeIndex < storage.Extruders.Count; extrudeIndex++) { // Only process the first extruder on spiral vase or // skip extruders that have empty layers if (config.ContinuousSpiralOuterPerimeter) { SliceLayer layer0 = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer0.Islands[0]?.IslandOutline); break; } // Add the layers outline to allOutlines SliceLayer layer = storage.Extruders[extrudeIndex].Layers[0]; allOutlines.AddAll(layer.AllOutlines); } if (brimCount > 0) { Polygons brimLoops = new Polygons(); // Loop over the requested brimCount creating and unioning a new perimeter for each island for (int brimIndex = 0; brimIndex < brimCount; brimIndex++) { int offsetDistance = extrusionWidth_um * brimIndex + extrusionWidth_um / 2; Polygons unionedIslandOutlines = new Polygons(); // Grow each island by the current brim distance foreach (var island in allOutlines) { var polygons = new Polygons(); polygons.Add(island); // Union the island brims unionedIslandOutlines = unionedIslandOutlines.CreateUnion(polygons.Offset(offsetDistance)); } // Extend the polygons to account for the brim (ensures convex hull takes this data into account) brimLoops.AddAll(unionedIslandOutlines); } // TODO: This is a quick hack, reuse the skirt data to stuff in the brim. Good enough from proof of concept storage.skirt.AddAll(brimLoops); skirtPolygons = skirtPolygons.CreateUnion(brimLoops); } else { skirtPolygons = skirtPolygons.CreateUnion(allOutlines); } if (storage.support != null) { skirtPolygons = skirtPolygons.CreateUnion(storage.support.GetBedOutlines()); } } return(skirtPolygons); }
public static int ProcessArgs(string[] args) { if (args.Length == 0) { print_usage(); return 0; } ConfigSettings config = new ConfigSettings(); fffProcessor processor = new fffProcessor(config); LogOutput.Log("\nMatterSlice version {0}\n\n".FormatWith(ConfigConstants.VERSION)); for (int argn = 0; argn < args.Length; argn++) { string str = args[argn]; if (str[0] == '-') { for (int stringIndex = 1; stringIndex < str.Length; stringIndex++) { switch (str[stringIndex]) { case 'h': print_usage(); return 0; case 'v': LogOutput.verbose_level++; break; case 'o': argn++; if (!processor.SetTargetFile(args[argn])) { LogOutput.LogError("Failed to open {0} for output.\n".FormatWith(args[argn])); return 1; } break; case 'c': { // Read a config file from the given path argn++; if (!config.ReadSettings(args[argn])) { LogOutput.LogError("Failed to read config '{0}'\n".FormatWith(args[argn])); } } break; case 'b': argn++; config.BooleanOpperations = args[argn]; break; case 'd': config.DumpSettings("settings.ini"); break; case 's': { argn++; int equalsPos = args[argn].IndexOf('='); if (equalsPos != -1) { string key = args[argn].Substring(0, equalsPos); string value = args[argn].Substring(equalsPos + 1); if (key.Length > 1) { if (!config.SetSetting(key, value)) { LogOutput.LogError("Setting not found: {0} {1}\n".FormatWith(key, value)); } } } } break; case 'm': argn++; throw new NotImplementedException("m"); #if false sscanf(argv[argn], "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf", &config.matrix.m[0][0], &config.matrix.m[0][1], &config.matrix.m[0][2], &config.matrix.m[1][0], &config.matrix.m[1][1], &config.matrix.m[1][2], &config.matrix.m[2][0], &config.matrix.m[2][1], &config.matrix.m[2][2]); #endif //break; default: throw new NotImplementedException("Unknown option: {0}\n".FormatWith(str)); //LogOutput.logError("Unknown option: {0}\n".FormatWith(str)); //break; } } } else { processor.LoadStlFile(args[argn]); } } if (!Canceled) { processor.DoProcessing(); } if (!Canceled) { processor.finalize(); } if (Canceled) { processor.Cancel(); } return 0; }
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, config.MergeOverlappingLines); 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, 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, config.MergeOverlappingLines); 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, config.MergeOverlappingLines); 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 static void GenerateHexagonInfill(ConfigSettings config, Polygons partOutline, ref Polygons fillPolygons, double fillAngle, int layerIndex) { if (config.infillPercent <= 0) { throw new Exception("infillPercent must be gerater than 0."); } int linespacing_um = (int)(config.extrusionWidth_um / (config.infillPercent / 100) * 3 * .66); Infill.GenerateHexLinePaths(partOutline, ref fillPolygons, linespacing_um, config.infillExtendIntoPerimeter_um, fillAngle, layerIndex); }
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 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; }
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; } }
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 TestCorrectSupportLayer() { // test the supports for a simple cube in the air { ConfigSettings config = new ConfigSettings(); config.layerThickness = .5; config.supportXYDistanceFromObject = 0; config.supportInterfaceLayers = 0; config.minimizeSupportColumns = false; List<Polygons> partOutlines = new List<Polygons>(); for (int i = 0; i < 5; i++) { partOutlines.Add(new Polygons()); } Polygons cubeOutline = PolygonsHelper.CreateFromString("x:0, y:0,x:10000, y:0,x:10000, y:10000,x:0, y:10000,|"); for (int i = 0; i < 5; i++) { partOutlines.Add(cubeOutline); } ExtruderLayers layerData = CreateLayerData(partOutlines); NewSupport supportGenerator = new NewSupport(config, new List<ExtruderLayers>() { layerData }, 0); Polygons cubeOutlineResults = PolygonsHelper.CreateFromString("x:200, y:200,x:9800, y:200,x:9800, y:9800,x:200, y:9800,|"); // check the all part outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, null, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, }; CheckLayers(supportGenerator.insetPartOutlines, polygonsCounts, polygon0Counts, poly0Paths); } // check the potential support outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, cubeOutlineResults, null, null, null, null, null }; CheckLayers(supportGenerator.allPotentialSupportOutlines, polygonsCounts, polygon0Counts, poly0Paths); } // check the required support outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, cubeOutlineResults, null, null, null, null, null }; CheckLayers(supportGenerator.allRequiredSupportOutlines, polygonsCounts, polygon0Counts, poly0Paths); } // check the generated support outlines { List<int> polygonsCounts = new List<int> { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, null, null, null, null, null }; CheckLayers(supportGenerator.supportOutlines, polygonsCounts, polygon0Counts, poly0Paths); } // check the interface support outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, null, null, null, null, null, null }; CheckLayers(supportGenerator.interfaceLayers, polygonsCounts, polygon0Counts, poly0Paths); } } // test the supports for a cube that is 1/2 width just under the main part { ConfigSettings config = new ConfigSettings(); config.supportInterfaceLayers = 0; config.layerThickness = .5; config.supportXYDistanceFromObject = .1; // 14 XXXXXXXXXXXXXXXXXXXX // 13 XXXXXXXXXXXXXXXXXXXX // 12 XXXXXXXXXXXXXXXXXXXX // 11 XXXXXXXXXXXXXXXXXXXX // 10 XXXXXXXXXXXXXXXXXXXX // 9 XXXXXXXXXX <- interface layer // 8 XXXXXXXXXX <- interface layer // 7 XXXXXXXXXX ^ - requires support // 6 XXXXXXXXXX // 5 XXXXXXXXXX // 4 <- interface layer // 3 <- interface layer // 2 ^ - requires support // 1 // 0 List<Polygons> partOutlines = new List<Polygons>(); for (int i = 0; i < 5; i++) { partOutlines.Add(new Polygons()); } Polygons halfCubeOutline = PolygonsHelper.CreateFromString("x:0, y:0,x:5000, y:0,x:5000, y:10000,x:0, y:10000,|"); Polygons halfCubeOutlineResults = halfCubeOutline.Offset(-200); for (int i = 0; i < 5; i++) { partOutlines.Add(halfCubeOutline); } Polygons cubeOutline = PolygonsHelper.CreateFromString("x:0, y:0,x:10000, y:0,x:10000, y:10000,x:0, y:10000,|"); Polygons cubeOutlineResults = cubeOutline.Offset(-200); for (int i = 0; i < 5; i++) { partOutlines.Add(cubeOutline); } ExtruderLayers layerData = CreateLayerData(partOutlines); NewSupport supportGenerator = new NewSupport(config, new List<ExtruderLayers>() { layerData }, 1); // check the all part outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,}; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,}; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, null, halfCubeOutlineResults, halfCubeOutlineResults, halfCubeOutlineResults, halfCubeOutlineResults, halfCubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, }; CheckLayers(supportGenerator.insetPartOutlines, polygonsCounts, polygon0Counts, poly0Paths); } Polygons layer9Support = PolygonsHelper.CreateFromString("x:5000, y:200,x:9800, y:200,x:9800, y:9800,x:5000, y:9800,|"); // check the potential support outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, halfCubeOutlineResults, null, null, null, null, layer9Support, null, null, null, null, null }; CheckLayers(supportGenerator.allPotentialSupportOutlines, polygonsCounts, polygon0Counts, poly0Paths); } // check the required support outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, halfCubeOutlineResults, null, null, null, null, layer9Support, null, null, null, null, null }; CheckLayers(supportGenerator.allRequiredSupportOutlines, polygonsCounts, polygon0Counts, poly0Paths); } if (false) { // check the generated support outlines { List<int> polygonsCounts = new List<int> { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 4, 4, 4, 4, 4, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, cubeOutlineResults, null, null, null, null, null }; CheckLayers(supportGenerator.supportOutlines, polygonsCounts, polygon0Counts, poly0Paths); } // check the interface support outlines { List<int> polygonsCounts = new List<int> { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; List<int> polygon0Counts = new List<int> { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; List<Polygons> poly0Paths = new List<Polygons>() { null, null, null, null, null, null, null, null, null, null }; CheckLayers(supportGenerator.interfaceLayers, polygonsCounts, polygon0Counts, poly0Paths); } } } }
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); } }
public void PrimeOnWipeTower(int layerIndex, LayerGCodePlanner layerGcodePlanner, GCodePathConfig fillConfig, ConfigSettings config, bool airGapped) { if (!HaveWipeTower(config) || layerIndex > LastLayerWithChange(config) + 1 || layerIndex == 0) { return; } if (airGapped) { // don't print the wipe tower with air gap height layerGcodePlanner.CurrentZ -= config.SupportAirGap_um; } //If we changed extruder, print the wipe/prime tower for this nozzle; Polygons fillPolygons = new Polygons(); var oldPathFinder = layerGcodePlanner.PathFinder; layerGcodePlanner.PathFinder = null; GenerateWipeTowerInfill(primesThisLayer, this.WipeTower, fillPolygons, fillConfig.LineWidthUM, config); layerGcodePlanner.QueuePolygons(fillPolygons, fillConfig); layerGcodePlanner.PathFinder = oldPathFinder; if (airGapped) { // don't print the wipe tower with air gap height layerGcodePlanner.CurrentZ += config.SupportAirGap_um; } primesThisLayer++; }