public void saveDebugSTL(string filename) { #if true OptimizedVolume vol = volumes[0]; using (StreamWriter stream = new StreamWriter(filename)) { BinaryWriter f = new BinaryWriter(stream.BaseStream); Byte[] header = new Byte[80]; f.Write(header); int n = vol.facesTriangle.Count; f.Write(n); for (int i = 0; i < vol.facesTriangle.Count; i++) { // stl expects a normal (we don't care about it's data) f.Write((float)1); f.Write((float)1); f.Write((float)1); for (int vert = 0; vert < 3; vert++) { f.Write((float)(vol.vertices[vol.facesTriangle[i].vertexIndex[vert]].position.x / 1000.0)); f.Write((float)(vol.vertices[vol.facesTriangle[i].vertexIndex[vert]].position.y / 1000.0)); f.Write((float)(vol.vertices[vol.facesTriangle[i].vertexIndex[vert]].position.z / 1000.0)); } f.Write((short)0); } } #else char buffer[80] = "MatterSlice_STL_export";
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; }
public Slicer(OptimizedVolume ov, int initialLayerThickness, int layerThickness, ConfigConstants.REPAIR_OUTLINES outlineRepairTypes) { modelSize = ov.model.size; modelMin = ov.model.minXYZ; int heightWithoutFirstLayer = modelSize.z - initialLayerThickness; int countOfNormalThicknessLayers = heightWithoutFirstLayer / layerThickness; int layerCount = countOfNormalThicknessLayers + 1; // we have to add in the first layer (that is a differnt size) LogOutput.log(string.Format("Layer count: {0}\n", layerCount)); layers.Capacity = layerCount; for (int i = 0; i < layerCount; i++) { layers.Add(new SlicerLayer()); } for (int layerIndex = 0; layerIndex < layerCount; layerIndex++) { if (layerIndex == 0) { layers[layerIndex].z = initialLayerThickness / 2; } else { layers[layerIndex].z = initialLayerThickness + layerThickness / 2 + layerThickness * (layerIndex - 1); } } 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 || layerIndex < 0) { 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; } layers[layerIndex].faceTo2DSegmentIndex[faceIndex] = layers[layerIndex].segmentList.Count; polyCrossingAtThisZ.faceIndex = faceIndex; polyCrossingAtThisZ.hasBeenAddedToPolygon = false; layers[layerIndex].segmentList.Add(polyCrossingAtThisZ); } } for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++) { layers[layerIndex].MakePolygons(ov, outlineRepairTypes); } }
public void MakePolygons(OptimizedVolume optomizedMesh, ConfigConstants.REPAIR_OUTLINES outlineRepairTypes) { for (int startingSegmentIndex = 0; startingSegmentIndex < segmentList.Count; startingSegmentIndex++) { if (segmentList[startingSegmentIndex].hasBeenAddedToPolygon) { continue; } Polygon poly = new Polygon(); // We start by adding the start, as we will add ends from now on. IntPoint polygonStartPosition = segmentList[startingSegmentIndex].start; poly.Add(polygonStartPosition); int segmentIndexBeingAdded = startingSegmentIndex; bool canClose; bool lastAddWasAStart = true; while (true) { canClose = false; segmentList[segmentIndexBeingAdded].hasBeenAddedToPolygon = true; IntPoint addedSegmentEndPoint = segmentList[segmentIndexBeingAdded].end; if (!lastAddWasAStart) { // the last added point was an end so add the start as the text end point addedSegmentEndPoint = segmentList[segmentIndexBeingAdded].start; } poly.Add(addedSegmentEndPoint); int nextSegmentToCheckIndex = -1; OptimizedFace face = optomizedMesh.facesTriangle[segmentList[segmentIndexBeingAdded].faceIndex]; for (int connectedFaceIndex = 0; connectedFaceIndex < 3; connectedFaceIndex++) { int testFaceIndex = face.touchingFaces[connectedFaceIndex]; if (testFaceIndex > -1) { // If the connected face has an edge that is in the segment list if (faceTo2DSegmentIndex.ContainsKey(testFaceIndex)) { int touchingSegmentIndex = faceTo2DSegmentIndex[testFaceIndex]; IntPoint foundSegmentStart = segmentList[touchingSegmentIndex].start; if (addedSegmentEndPoint == foundSegmentStart) { // if we have looped back around to where we started if (addedSegmentEndPoint == polygonStartPosition) { canClose = true; } // If this segment has already been added if (segmentList[touchingSegmentIndex].hasBeenAddedToPolygon) { continue; } nextSegmentToCheckIndex = touchingSegmentIndex; lastAddWasAStart = true; } else // let's check if the other side of this segment can hook up (the normal is facing the wrong way) { IntPoint foundSegmentEnd = segmentList[touchingSegmentIndex].end; if (addedSegmentEndPoint == foundSegmentEnd) { // if we have looped back around to where we started if (addedSegmentEndPoint == polygonStartPosition) { canClose = true; } // If this segment has already been added if (segmentList[touchingSegmentIndex].hasBeenAddedToPolygon) { continue; } nextSegmentToCheckIndex = touchingSegmentIndex; lastAddWasAStart = false; } } } } } if (nextSegmentToCheckIndex == -1) { break; } segmentIndexBeingAdded = nextSegmentToCheckIndex; } if (canClose) { polygonList.Add(poly); } else { openPolygonList.Add(poly); } } // Link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time. while (true) { long bestScore = 10000 * 10000; int bestA = -1; int bestB = -1; bool reversed = false; for (int polygonAIndex = 0; polygonAIndex < openPolygonList.Count; polygonAIndex++) { if (openPolygonList[polygonAIndex].Count < 1) { continue; } for (int polygonBIndex = 0; polygonBIndex < openPolygonList.Count; polygonBIndex++) { if (openPolygonList[polygonBIndex].Count < 1) { continue; } IntPoint diff1 = openPolygonList[polygonAIndex][openPolygonList[polygonAIndex].Count - 1] - openPolygonList[polygonBIndex][0]; long distSquared1 = (diff1).LengthSquared(); if (distSquared1 < bestScore) { bestScore = distSquared1; bestA = polygonAIndex; bestB = polygonBIndex; reversed = false; if (bestScore == 0) { // found a perfect match stop looking break; } } if (polygonAIndex != polygonBIndex) { IntPoint diff2 = openPolygonList[polygonAIndex][openPolygonList[polygonAIndex].Count - 1] - openPolygonList[polygonBIndex][openPolygonList[polygonBIndex].Count - 1]; long distSquared2 = (diff2).LengthSquared(); if (distSquared2 < bestScore) { bestScore = distSquared2; bestA = polygonAIndex; bestB = polygonBIndex; reversed = true; if (bestScore == 0) { // found a perfect match stop looking break; } } } } if (bestScore == 0) { // found a perfect match stop looking break; } } if (bestScore >= 10000 * 10000) { break; } if (bestA == bestB) { polygonList.Add(new Polygon(openPolygonList[bestA])); openPolygonList[bestA].Clear(); } else { if (reversed) { if (openPolygonList[bestA].PolygonLength() > openPolygonList[bestB].PolygonLength()) { if (openPolygonList[bestA].PolygonLength() > openPolygonList[bestB].PolygonLength()) { openPolygonList[bestA].AddRange(openPolygonList[bestB]); openPolygonList[bestB].Clear(); } else { openPolygonList[bestB].AddRange(openPolygonList[bestA]); openPolygonList[bestA].Clear(); } } else { openPolygonList[bestB].AddRange(openPolygonList[bestA]); openPolygonList[bestB].Clear(); } } else { openPolygonList[bestA].AddRange(openPolygonList[bestB]); openPolygonList[bestB].Clear(); } } } if ((outlineRepairTypes & ConfigConstants.REPAIR_OUTLINES.EXTENSIVE_STITCHING) == ConfigConstants.REPAIR_OUTLINES.EXTENSIVE_STITCHING) { //For extensive stitching find 2 open polygons that are touching 2 closed polygons. // Then find the sortest path over this polygon that can be used to connect the open polygons, // And generate a path over this shortest bit to link up the 2 open polygons. // (If these 2 open polygons are the same polygon, then the final result is a closed polyon) while (true) { int bestA = -1; int bestB = -1; GapCloserResult bestResult = new GapCloserResult(); bestResult.len = long.MaxValue; bestResult.polygonIndex = -1; bestResult.pointIndexA = -1; bestResult.pointIndexB = -1; for (int i = 0; i < openPolygonList.Count; i++) { if (openPolygonList[i].Count < 1) { continue; } { GapCloserResult res = FindPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].Count - 1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = i; bestResult = res; } } for (int j = 0; j < openPolygonList.Count; j++) { if (openPolygonList[j].Count < 1 || i == j) { continue; } GapCloserResult res = FindPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].Count - 1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = j; bestResult = res; } } } if (bestResult.len < long.MaxValue) { if (bestA == bestB) { if (bestResult.pointIndexA == bestResult.pointIndexB) { polygonList.Add(new Polygon(openPolygonList[bestA])); openPolygonList[bestA].Clear(); } else if (bestResult.AtoB) { Polygon poly = new Polygon(); polygonList.Add(poly); for (int j = bestResult.pointIndexA; j != bestResult.pointIndexB; j = (j + 1) % polygonList[bestResult.polygonIndex].Count) { poly.Add(polygonList[bestResult.polygonIndex][j]); } poly.AddRange(openPolygonList[bestA]); openPolygonList[bestA].Clear(); } else { int n = polygonList.Count; polygonList.Add(new Polygon(openPolygonList[bestA])); for (int j = bestResult.pointIndexB; j != bestResult.pointIndexA; j = (j + 1) % polygonList[bestResult.polygonIndex].Count) { polygonList[n].Add(polygonList[bestResult.polygonIndex][j]); } openPolygonList[bestA].Clear(); } } else { if (bestResult.pointIndexA == bestResult.pointIndexB) { openPolygonList[bestB].AddRange(openPolygonList[bestA]); openPolygonList[bestA].Clear(); } else if (bestResult.AtoB) { Polygon poly = new Polygon(); for (int n = bestResult.pointIndexA; n != bestResult.pointIndexB; n = (n + 1) % polygonList[bestResult.polygonIndex].Count) { poly.Add(polygonList[bestResult.polygonIndex][n]); } for (int n = poly.Count - 1; (int)(n) >= 0; n--) { openPolygonList[bestB].Add(poly[n]); } for (int n = 0; n < openPolygonList[bestA].Count; n++) { openPolygonList[bestB].Add(openPolygonList[bestA][n]); } openPolygonList[bestA].Clear(); } else { for (int n = bestResult.pointIndexB; n != bestResult.pointIndexA; n = (n + 1) % polygonList[bestResult.polygonIndex].Count) { openPolygonList[bestB].Add(polygonList[bestResult.polygonIndex][n]); } for (int n = openPolygonList[bestA].Count - 1; n >= 0; n--) { openPolygonList[bestB].Add(openPolygonList[bestA][n]); } openPolygonList[bestA].Clear(); } } } else { break; } } } if ((outlineRepairTypes & ConfigConstants.REPAIR_OUTLINES.KEEP_OPEN) == ConfigConstants.REPAIR_OUTLINES.KEEP_OPEN) { for (int n = 0; n < openPolygonList.Count; n++) { if (openPolygonList[n].Count > 0) { polygonList.Add(new Polygon(openPolygonList[n])); } } } //Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print. int snapDistance = 1000; for (int polygonIndex = 0; polygonIndex < polygonList.Count; polygonIndex++) { int length = 0; for (int intPointIndex = 1; intPointIndex < polygonList[polygonIndex].Count; intPointIndex++) { length += (polygonList[polygonIndex][intPointIndex] - polygonList[polygonIndex][intPointIndex - 1]).vSize(); if (length > snapDistance) { break; } } if (length < snapDistance) { polygonList.RemoveAt(polygonIndex); polygonIndex--; } } //Finally optimize all the polygons. Every point removed saves time in the long run. double minimumDistanceToCreateNewPosition = 1.415; polygonList = Clipper.CleanPolygons(polygonList, minimumDistanceToCreateNewPosition); }
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 = 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 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); } }