diff --git a/MatterControlLib/DesignTools/SupportGenerator.cs b/MatterControlLib/DesignTools/SupportGenerator.cs index ef06c3115..128e95983 100644 --- a/MatterControlLib/DesignTools/SupportGenerator.cs +++ b/MatterControlLib/DesignTools/SupportGenerator.cs @@ -33,7 +33,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MatterHackers.Agg; -using MatterHackers.Agg.Image; using MatterHackers.DataConverters3D; using MatterHackers.MatterControl.PartPreviewWindow; using MatterHackers.PolygonMesh; @@ -42,46 +41,11 @@ using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.DesignTools { - public static class FaceListExtensions - { - public static IPrimitive CreateTraceData(this FaceList faceList, List vertexList, int maxRecursion = int.MaxValue) - { - var allPolys = new List(); - - foreach (var face in faceList) - { - allPolys.Add(new TriangleShape(vertexList[face.v0], vertexList[face.v1], vertexList[face.v2], null)); - } - - return BoundingVolumeHierarchy.CreateNewHierachy(allPolys, maxRecursion); - } - - public static IPrimitive CreateTraceData(this FaceList faceList, List vertexList, int maxRecursion = int.MaxValue) - { - var allPolys = new List(); - - foreach (var face in faceList) - { - allPolys.Add(new TriangleShape(vertexList[face.v0], vertexList[face.v1], vertexList[face.v2], null)); - } - - return BoundingVolumeHierarchy.CreateNewHierachy(allPolys, maxRecursion); - } - } - - [HideFromTreeViewAttribute, Immutable] - public class GeneratedSupportObject3D : Object3D - { - public GeneratedSupportObject3D() - { - OutputType = PrintOutputTypes.Support; - } - } - public class SupportGenerator { - private double minimumSupportHeight; - private InteractiveScene scene; + private readonly InteractiveScene scene; + + private readonly double minimumSupportHeight; public SupportGenerator(InteractiveScene scene, double minimumSupportHeight) { @@ -89,7 +53,16 @@ namespace MatterHackers.MatterControl.DesignTools this.scene = scene; } - public enum SupportGenerationType { Normal, From_Bed } + public enum SupportGenerationType + { + Normal, + From_Bed + } + + /// + /// Gets the amount to reduce the pillars so they are separated in the 3D view. + /// + public static double ColumnReduceAmount => 1; public double MaxOverHangAngle { @@ -99,11 +72,13 @@ namespace MatterHackers.MatterControl.DesignTools { return 45; } + var value = UserSettings.Instance.GetValue(UserSettingsKey.SupportMaxOverHangAngle); if (value < 0) { return 0; } + if (value > 90) { value = 90; @@ -130,6 +105,7 @@ namespace MatterHackers.MatterControl.DesignTools return value; } + set { UserSettings.Instance.set(UserSettingsKey.SupportPillarSize, value.ToString()); @@ -155,10 +131,17 @@ namespace MatterHackers.MatterControl.DesignTools } } - /// - /// The amount to reduce the pillars so they are separated in the 3D view - /// - public static double ColumnReduceAmount => 1; + public static IPrimitive CreateTraceData(FaceList faceList, List vertexList, int maxRecursion = int.MaxValue) + { + var allPolys = new List(); + + foreach (var face in faceList) + { + allPolys.Add(new TriangleShape(vertexList[face.v0], vertexList[face.v1], vertexList[face.v2], null)); + } + + return BoundingVolumeHierarchy.CreateNewHierachy(allPolys, maxRecursion); + } public Task Create(IProgress progress, CancellationToken cancelationToken) { @@ -166,14 +149,16 @@ namespace MatterHackers.MatterControl.DesignTools using (new SelectionMaintainer(scene)) { - ProgressStatus status = new ProgressStatus(); - status.Status = "Enter"; + var status = new ProgressStatus + { + Status = "Enter" + }; progress?.Report(status); - // Get visible meshes for each of them + // Get visible meshes for each of them var allBedItems = scene.Children.SelectMany(i => i.VisibleMeshes()); - AxisAlignedBoundingBox suppoortBounds = AxisAlignedBoundingBox.Empty(); + var suppoortBounds = AxisAlignedBoundingBox.Empty(); if (selectedItem != null) { foreach (var candidate in selectedItem.VisibleMeshes()) @@ -289,6 +274,7 @@ namespace MatterHackers.MatterControl.DesignTools break; } } + if (aboveBed) { var face0Normal = item.Mesh.Faces[faceIndex].normal.TransformNormal(matrix).GetNormal(); @@ -314,6 +300,7 @@ namespace MatterHackers.MatterControl.DesignTools // less than 10 micros high, don't ad it return; } + var support = new GeneratedSupportObject3D() { Mesh = PlatonicSolids.CreateCube(1, 1, 1) @@ -326,80 +313,37 @@ namespace MatterHackers.MatterControl.DesignTools holder.Children.Add(support); } - private void AddSupportColumns(RectangleDouble gridBounds, Dictionary<(int x, int y), List<(double z, bool bottom)>> detectedPlanes) + private void AddSupportColumns(RectangleDouble gridBounds, Dictionary<(int x, int y), SupportColumn> supportGrid) { IObject3D supportColumnsToAdd = new Object3D(); bool fromBed = SupportType == SupportGenerationType.From_Bed; var halfPillar = PillarSize / 2; - foreach (var kvp in detectedPlanes) + foreach (var kvp in supportGrid) { - var planes = kvp.Value; + var supportColumnData = kvp.Value; - if (planes.Count == 0) + if (supportColumnData.Count == 0) { continue; } - planes.Sort((a, b) => - { - return a.z.CompareTo(b.z); - }); - var yPos = (gridBounds.Bottom + kvp.Key.y) * PillarSize + halfPillar; var xPos = (gridBounds.Left + kvp.Key.x) * PillarSize + halfPillar; if (fromBed) { - var nextPlaneIsBottom = planes.Count > 1 && planes[1].bottom; - if (!nextPlaneIsBottom // if the next plane is a top, we don't have any space from the bed to the part to put support - || planes[1].z > minimumSupportHeight) // if the next plane is a bottom and is not far enough away, there is no space to put any support + // if the next plane is a bottom and is not far enough away, there is no space to put any support + if (supportColumnData[0].start < minimumSupportHeight + && supportColumnData[0].end > minimumSupportHeight) { - var firstBottomAboveBed = GetNextBottom(0, planes, minimumSupportHeight); - - if (firstBottomAboveBed >= 0) - { - AddSupportColumn(supportColumnsToAdd, xPos, yPos, 0, planes[firstBottomAboveBed].z + .01); - } + AddSupportColumn(supportColumnsToAdd, xPos, yPos, 0, supportColumnData[0].end + .01); } } else { - int i = 0; - double lastTopZ = 0; - int lastBottom = -1; - var nextPlaneIsBottom = planes.Count > 1 && planes[1].bottom; - // if the next plane (the one above the bed) is a bottom, we have a part on the bed and will not generate support - if (nextPlaneIsBottom && planes[1].z <= minimumSupportHeight) + foreach (var (start, end) in supportColumnData) { - // go up to the next top - i = GetNextTop(i, planes, minimumSupportHeight); - if (i >= 0) - { - lastTopZ = planes[i].z; - } - } - - while (i != -1 - && i != lastBottom - && i < planes.Count) - { - lastBottom = i; - // find all open areas in the list and add support - i = GetNextBottom(i, planes, minimumSupportHeight); - if (i >= 0) - { - if (i < planes.Count - && planes[i].bottom) - { - AddSupportColumn(supportColumnsToAdd, xPos, yPos, lastTopZ, planes[i].z); - } - i = GetNextTop(i + 1, planes, minimumSupportHeight); - if (i >= 0 - && i < planes.Count) - { - lastTopZ = planes[i].z; - } - } + AddSupportColumn(supportColumnsToAdd, xPos, yPos, start, end); } } } @@ -407,7 +351,32 @@ namespace MatterHackers.MatterControl.DesignTools scene.UndoBuffer.AddAndDo(new InsertCommand(scene, supportColumnsToAdd.Children, false)); } - private Dictionary<(int x, int y), List<(double z, bool bottom)>> DetectRequiredSupportByTracing(RectangleDouble gridBounds, IEnumerable supportCandidates) + private void AddSupportFaces(IEnumerable supportCandidates, List supportVerts, FaceList supportFaces) + { + foreach (var item in supportCandidates) + { + // add all the faces + var matrix = item.WorldMatrix(scene); + for (int faceIndex = 0; faceIndex < item.Mesh.Faces.Count; faceIndex++) + { + var face0Normal = item.Mesh.Faces[faceIndex].normal.TransformNormal(matrix).GetNormal(); + + var face = item.Mesh.Faces[faceIndex]; + var verts = new int[] { face.v0, face.v1, face.v2 }; + var p0 = item.Mesh.Vertices[face.v0].Transform(matrix); + var p1 = item.Mesh.Vertices[face.v1].Transform(matrix); + var p2 = item.Mesh.Vertices[face.v2].Transform(matrix); + var vc = supportVerts.Count; + supportVerts.Add(p0); + supportVerts.Add(p1); + supportVerts.Add(p2); + + supportFaces.Add(vc, vc + 1, vc + 2, face0Normal); + } + } + } + + private Dictionary<(int x, int y), SupportColumn> DetectRequiredSupportByTracing(RectangleDouble gridBounds, IEnumerable supportCandidates) { var allBounds = supportCandidates.GetAxisAlignedBoundingBox(); var rayStartZ = allBounds.MinXYZ.Z - 1; @@ -415,148 +384,81 @@ namespace MatterHackers.MatterControl.DesignTools var traceData = GetTraceData(supportCandidates); // keep a list of all the detected planes in each support column - var detectedPlanes = new Dictionary<(int x, int y), List<(double z, bool bottom)>>(); + var supportColumnData = new Dictionary<(int x, int y), SupportColumn>(); int gridWidth = (int)gridBounds.Width; int gridHeight = (int)gridBounds.Height; + var offset = new Vector3(.000013, .00027, 0); + // at the center of every grid item add in a list of all the top faces to look down from for (int y = 0; y < gridHeight; y++) { for (int x = 0; x < gridWidth; x++) { - // add a single plane at the bed so we always know the bed is a top - detectedPlanes.Add((x, y), new List<(double z, bool bottom)>()); - detectedPlanes[(x, y)].Add((0, false)); - - IntersectInfo upHit = null; + var supportColumn = new SupportColumn(minimumSupportHeight); + supportColumnData.Add((x, y), supportColumn); + // create support plans at this xy for (double yOffset = -1; yOffset <= 1; yOffset++) { for (double xOffset = -1; xOffset <= 1; xOffset++) { + var thisTracePlanes = new HitPlanes(minimumSupportHeight); var halfPillar = PillarSize / 2; var yPos = (gridBounds.Bottom + y) * PillarSize + halfPillar + (yOffset * halfPillar); var xPos = (gridBounds.Left + x) * PillarSize + halfPillar + (xOffset * halfPillar); // detect all the bottom plans (surfaces that might need support - var upRay = new Ray(new Vector3(xPos + .000013, yPos - .00027, rayStartZ), Vector3.UnitZ, intersectionType: IntersectionType.FrontFace); + var upRay = new Ray(new Vector3(xPos, yPos, rayStartZ) + offset, Vector3.UnitZ, intersectionType: IntersectionType.FrontFace); + IntersectInfo upHit; do { upHit = traceData.GetClosestIntersection(upRay); if (upHit != null) { - detectedPlanes[(x, y)].Add((upHit.HitPosition.Z, true)); + var angle = MathHelper.RadiansToDegrees(Math.Acos(upHit.normalAtHit.Dot(-Vector3.UnitZ))); + thisTracePlanes.Add(new HitPlane(upHit.HitPosition.Z, angle)); // make a new ray just past the last hit to keep looking for up hits - upRay = new Ray(new Vector3(xPos, yPos, upHit.HitPosition.Z + .001), Vector3.UnitZ, intersectionType: IntersectionType.FrontFace); + upRay = new Ray(new Vector3(xPos, yPos, upHit.HitPosition.Z + .001) + offset, Vector3.UnitZ, intersectionType: IntersectionType.FrontFace); } - } while (upHit != null); + } + while (upHit != null); // detect all the up plans (surfaces that will have support on top of them) - upRay = new Ray(new Vector3(xPos + .000013, yPos - .00027, rayStartZ), Vector3.UnitZ, intersectionType: IntersectionType.BackFace); + upRay = new Ray(new Vector3(xPos, yPos, rayStartZ) + offset, Vector3.UnitZ, intersectionType: IntersectionType.BackFace); do { upHit = traceData.GetClosestIntersection(upRay); if (upHit != null) { - detectedPlanes[(x, y)].Add((upHit.HitPosition.Z, false)); + var angle = MathHelper.RadiansToDegrees(Math.Acos(upHit.normalAtHit.Dot(-Vector3.UnitZ))); + thisTracePlanes.Add(new HitPlane(upHit.HitPosition.Z, angle)); // make a new ray just past the last hit to keep looking for up hits - upRay = new Ray(new Vector3(xPos, yPos, upHit.HitPosition.Z + .001), Vector3.UnitZ, intersectionType: IntersectionType.BackFace); + upRay = new Ray(new Vector3(xPos, yPos, upHit.HitPosition.Z + .001) + offset, Vector3.UnitZ, intersectionType: IntersectionType.BackFace); } - } while (upHit != null); + } + while (upHit != null); + + var debugPlanes = new HitPlanes(minimumSupportHeight); + debugPlanes.AddRange(thisTracePlanes); + debugPlanes.Sort(MaxOverHangAngle); + var lineSupport = new SupportColumn(thisTracePlanes, minimumSupportHeight, MaxOverHangAngle); + + if (lineSupport.Count > 0) + { + int a = 0; + } + + supportColumn.Union(lineSupport); } } } } - return detectedPlanes; - } - - public static int GetNextBottom(int i, List<(double z, bool bottom)> planes, double skipDist) - { - while (i < planes.Count) - { - // if we are on a bottom - if (planes[i].bottom) - { - // move up to the next plane and re-evaluate - i++; - } - else // we are on a top - { - // if the next plane is a bottom and more than skipDistanc away - if (i + 1 < planes.Count - && planes[i + 1].bottom - && planes[i + 1].z > planes[i].z + skipDist) - { - // this is the next bottom we are looking for - return i + 1; - } - else // move up to the next plane and re-evaluate - { - i++; - } - } - } - - return -1; - } - - public static int GetNextTop(int i, List<(double z, bool bottom)> planes, double skipDist) - { - if (!planes[i].bottom) - { - // skip the one we are - i++; - } - - while (i < planes.Count) - { - // if we are on a bottom - if (planes[i].bottom) - { - // move up to the next plane and re-evaluate - i++; - } - else // we are on a top - { - // if the next plane is a bottom and more than skipDistanc away - if (i + 1 < planes.Count - && planes[i + 1].bottom - && planes[i + 1].z > planes[i].z + skipDist) - { - // this is the next top we are looking for - return i; - } - else // move up to the next plane and re-evaluate - { - i++; - } - } - } - - return -1; - } - - // function to get all the columns that need support generation - private IEnumerable<(int x, int y)> GetSupportCorrodinates(ImageBuffer supportNeededImage) - { - var buffer = supportNeededImage.GetBuffer(); - // check if the image has any alpha set to something other than 255 - for (int y = 0; y < supportNeededImage.Height; y++) - { - var yOffset = supportNeededImage.GetBufferOffsetY(y); - for (int x = 0; x < supportNeededImage.Width; x++) - { - // get the alpha at this pixel - //if (buffer[yOffset + x] > 0) - { - yield return (x, y); - } - } - } + return supportColumnData; } private IPrimitive GetTraceData(IEnumerable supportCandidates) @@ -568,48 +470,387 @@ namespace MatterHackers.MatterControl.DesignTools supportVerts = new List(); supportFaces = new FaceList(); - // find all the down faces from the support candidates + // find all the faces from the support candidates AddSupportFaces(supportCandidates, supportVerts, - supportFaces, - (angle) => angle <= MaxOverHangAngle); + supportFaces); - // find all the up faces from everything on the bed - AddSupportFaces(scene.Children.SelectMany(i => i.VisibleMeshes()), - supportVerts, - supportFaces, - (angle) => angle >= 90); - - return supportFaces.CreateTraceData(supportVerts); + return CreateTraceData(supportFaces, supportVerts); } - private void AddSupportFaces(IEnumerable supportCandidates, List supportVerts, FaceList supportFaces, Func doAdd) + public struct HitPlane { - foreach (var item in supportCandidates) + public double Z; + + public HitPlane(double z, bool bottom) + : this(z, bottom ? 0 : 180) { - // add all the down faces to supportNeededImage - var matrix = item.WorldMatrix(scene); - for (int faceIndex = 0; faceIndex < item.Mesh.Faces.Count; faceIndex++) + } + + public HitPlane(double z, double angle) + { + this.Z = z; + + this.Angle = angle; + } + + public bool Bottom(double maxOverHangAngle = 45) + { + return Angle <= maxOverHangAngle; + } + + public double Angle { get; set; } + + public bool Top(double maxOverHangAngle = 45) + { + return Angle > maxOverHangAngle; + } + + public override string ToString() + { + return $"Z={Z:0.###} {(Bottom(45) ? "Bottom" : "Top")}"; + } + } + + [HideFromTreeViewAttribute, Immutable] + public class GeneratedSupportObject3D : Object3D + { + public GeneratedSupportObject3D() + { + OutputType = PrintOutputTypes.Support; + } + } + + public class SupportColumn : List<(double start, double end)> + { + private readonly double minimumSupportHeight; + + /// + /// Initializes a new instance of the class. + /// + /// The minimum distance between support regions. + public SupportColumn(double minimumSupportHeight) + { + this.minimumSupportHeight = minimumSupportHeight; + } + + /// + /// Initializes a new instance of the class. + /// + /// The planes to consider while creating the support regions. + /// The minimum distance between support regions. + /// The maximum angle that will be treated as a bottom. + public SupportColumn(HitPlanes inputPlanes, double minimumSupportHeight, double maxOverHangAngle = 45) + : this(minimumSupportHeight) + { + var hitPlanes = new HitPlanes(inputPlanes.MinimumSupportHeight); + hitPlanes.AddRange(inputPlanes); + hitPlanes.Simplify(maxOverHangAngle); + + var i = 0; + var currentTop = 0.0; + // if the first bottom is more than the min distance + if (hitPlanes.Count > 1 && hitPlanes[i].Z <= minimumSupportHeight) { - var face0Normal = item.Mesh.Faces[faceIndex].normal.TransformNormal(matrix).GetNormal(); - var angle = MathHelper.RadiansToDegrees(Math.Acos(face0Normal.Dot(-Vector3Float.UnitZ))); + currentTop = hitPlanes[i + 1].Z; + i += 2; + } - if (doAdd(angle)) + for (; i < hitPlanes.Count / 2 * 2; i += 2) + { + if (hitPlanes[i].Z > currentTop + minimumSupportHeight) { - var face = item.Mesh.Faces[faceIndex]; - var verts = new int[] { face.v0, face.v1, face.v2 }; - var p0 = item.Mesh.Vertices[face.v0].Transform(matrix); - var p1 = item.Mesh.Vertices[face.v1].Transform(matrix); - var p2 = item.Mesh.Vertices[face.v2].Transform(matrix); - var vc = supportVerts.Count; - supportVerts.Add(p0); - supportVerts.Add(p1); - supportVerts.Add(p2); + this.Add((currentTop, hitPlanes[i].Z)); + currentTop = hitPlanes[i + 1].Z; + } + } + } - supportFaces.Add(vc, vc + 1, vc + 2, face0Normal); + public void Union(SupportColumn other) + { + if (this.Count == 0) + { + this.AddRange(other); + return; + } + + // merge them, considering minimumSupportHeight + for (int i = 0; i < this.Count; i++) + { + for (int j = 0; j < other.Count; j++) + { + // check if they overlap and other is not completely contained in this + if (this[i].start <= other[j].end + minimumSupportHeight + && this[i].end >= other[j].start - minimumSupportHeight + && (this[i].start > other[j].start || this[i].end < other[j].end)) + { + // set this range to be the union + this[i] = (Math.Min(this[i].start, other[j].start), + Math.Max(this[i].end, other[j].end)); + // fix up the planes in this + this.RemoveOverLaps(); + // and start at the beginning again + i--; + // drop out of the j loop + break; + } + else if (this[i].end < other[j].start + && i < this.Count - 1 + && this[i + 1].start > other[j].end) + { + // we are beyond the end of this + // add every additional set and return + this.Insert(i + 1, other[j]); + this.RemoveOverLaps(); + i--; + // drop out of the j loop + break; + } + } + } + } + + private void RemoveOverLaps() + { + // merge them, considering minimumSupportHeight + for (int i = 0; i < this.Count; i++) + { + for (int j = i + 1; j < this.Count; j++) + { + // check this is an overlap with the next segment + if (this[i].start <= this[j].end + minimumSupportHeight + && this[i].end >= this[j].start - minimumSupportHeight + && (this[i].start >= this[j].start || this[i].end <= this[j].end)) + { + // set this range to be the union + this[i] = (Math.Min(this[i].start, this[j].start), + Math.Max(this[i].end, this[j].end)); + // fix up the planes in this + this.RemoveAt(j); + // and start at the beginning again + i--; + // drop out of the j loop + break; + } } } } } + + public class HitPlanes : List + { + public double MinimumSupportHeight { get; private set; } + + public HitPlanes(double minimumSupportHeight) + { + this.MinimumSupportHeight = minimumSupportHeight; + } + + public int GetNextBottom(int i, double maxOverHangAngle = 45) + { + while (i < this.Count) + { + // if we are on a bottom + if (this[i].Bottom(maxOverHangAngle)) + { + // move up to the next plane and re-evaluate + i++; + } + else // we are on a top + { + // if the next plane is a bottom and more than minimumSupportHeight away + if (i + 1 < this.Count + && this[i + 1].Bottom(maxOverHangAngle) + && this[i + 1].Z > this[i].Z + MinimumSupportHeight) + { + // this is the next bottom we are looking for + return i + 1; + } + else // move up to the next plane and re-evaluate + { + i++; + } + } + } + + return -1; + } + + public int GetNextTop(int start, double maxOverHangAngle = 45) + { + var i = start; + + if (this.Count > 0 + && !this[i].Bottom(maxOverHangAngle)) + { + // skip the one we are + i++; + } + + return AdvanceToTop(i, start, maxOverHangAngle); + } + + public new void Sort() + { + throw new NotImplementedException("Call Sort(double maxOverHangAngle) instead"); + } + + public void Sort(double maxOverHangAngle) + { + this.Sort((a, b) => + { + // one is a top and the other is a bottom, sort by tops first + if (((a.Top(maxOverHangAngle) && b.Bottom(maxOverHangAngle)) || (a.Bottom(maxOverHangAngle) && b.Top(maxOverHangAngle))) + && a.Z < b.Z + MinimumSupportHeight / 2 + && a.Z > b.Z - MinimumSupportHeight / 2) + { + return a.Top(MinimumSupportHeight) ? 1 : -1; + } + + return a.Z.CompareTo(b.Z); + }); + } + + /// + /// Modify the list to have Bottom - Top, Bottom - Top items exactly. + /// Remove any internal Planes that are not required. This may reduce the set to no items. + /// + /// The max angle to consider a bottom. + public void Simplify(double maxOverHangAngle = 45) + { + // sort the list on Z + this.Sort(maxOverHangAngle); + + var highestPlane = double.NegativeInfinity; + var lastRemoveWasBottom = false; + // remove anything that is below 0 + while (Count > 0 + && this[0].Z < 0) + { + if (this[0].Z > highestPlane) + { + highestPlane = this[0].Z; + lastRemoveWasBottom = this[0].Bottom(maxOverHangAngle); + } + + this.RemoveAt(0); + } + + // if the first item is a top then add a bottom at 0 + if ((Count > 0 && this[0].Top(maxOverHangAngle) + && this[0].Z > 0) + || lastRemoveWasBottom) + { + this.Insert(0, new HitPlane(0, true)); + } + + // if the first item is still a top, remove it + while (Count > 0 + && this[0].Top(maxOverHangAngle)) + { + this.RemoveAt(0); + } + + // remove any items that are between a bottom and a top + int currentBottom = 0; + + while (Count > currentBottom + && currentBottom != -1) + { + var top = GetNextTop(currentBottom, maxOverHangAngle); + if (top != -1) + { + // remove everything between the bottom and the top + for (int i = top - 1; i > currentBottom; i--) + { + this.RemoveAt(i); + } + + top = currentBottom + 1; + if (this[top].Z - this[currentBottom].Z < MinimumSupportHeight) + { + // also remove the top + this.RemoveAt(top); + } + else + { + // move the bottom up past the current top + currentBottom = GetNextBottom(top, maxOverHangAngle); + + // remove everything between the bottom and the new top + if (currentBottom != -1) + { + for (int i = currentBottom - 1; i > top; i--) + { + this.RemoveAt(i); + } + } + + currentBottom = top + 1; + } + } + else // not another top + { + // if the last plane is a bottom add a top above it at the minimum distance + if (this.Count > 0 + && this[this.Count - 1].Bottom(maxOverHangAngle)) + { + var topHeight = this[this.Count - 1].Z + MinimumSupportHeight; + // remove all the bottoms from current up to last (but keep the actual last) + for (int i = this.Count - 1; i > currentBottom; i--) + { + this.RemoveAt(i); + } + + // add a top + this.Add(new HitPlane(topHeight, false)); + } + + break; + } + } + } + + private int AdvanceToTop(int i, int start, double maxOverHangAngle) + { + while (i < this.Count) + { + // if we are on a bottom + if (this[i].Bottom(maxOverHangAngle)) + { + // move up to the next plane and re-evaluate + i++; + } + else // we are on a top + { + // if the next plane is a bottom and more than minimumSupportHeight away + if (i + 1 < this.Count + && this[i + 1].Bottom(maxOverHangAngle) + && this[i + 1].Z > this[i].Z + MinimumSupportHeight) + { + // this is the next top we are looking for + return i; + } + else // move up to the next plane and re-evaluate + { + // if we started on a bottom + // and we are the last top + // and we are far enough away from the start bottom + if (this[start].Bottom(maxOverHangAngle) + && i == this.Count - 1 + && this[i].Z - this[start].Z > MinimumSupportHeight) + { + // we are on the last top of the part and have move up from some other part + return i; + } + + i++; + } + } + } + + return -1; + } + } } } \ No newline at end of file diff --git a/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs b/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs index 26fe7cf19..e39b20215 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs @@ -447,7 +447,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow foreach (var child in sceneContext.Scene.Children) { - if (child is GeneratedSupportObject3D) + if (child is SupportGenerator.GeneratedSupportObject3D) { continue; } diff --git a/MatterControlLib/SlicerConfiguration/Slicer.cs b/MatterControlLib/SlicerConfiguration/Slicer.cs index 07fc8f0a2..8fcb7b596 100644 --- a/MatterControlLib/SlicerConfiguration/Slicer.cs +++ b/MatterControlLib/SlicerConfiguration/Slicer.cs @@ -176,7 +176,7 @@ namespace MatterHackers.MatterControl.SlicerConfiguration // TODO: Use existing AssetsPath property string assetsDirectory = Path.Combine(ApplicationDataStorage.Instance.ApplicationLibraryDataPath, "Assets"); var itemWorldMatrix = item.WorldMatrix(); - if (item is GeneratedSupportObject3D generatedSupportObject3D) + if (item is SupportGenerator.GeneratedSupportObject3D generatedSupportObject3D) { // grow the support columns by the amount they are reduced by var aabb = item.GetAxisAlignedBoundingBox(); diff --git a/Submodules/MatterSlice b/Submodules/MatterSlice index 95d5991cf..cd05ff5df 160000 --- a/Submodules/MatterSlice +++ b/Submodules/MatterSlice @@ -1 +1 @@ -Subproject commit 95d5991cf3325b19dec5b1ece1fad940989a7039 +Subproject commit cd05ff5df6b486fcf3bae2bcef848021a52d1046 diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 42e665205..da3b47918 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 42e6652050dfce45e2c158509839db15eb8a5886 +Subproject commit da3b47918b41e20d1bdd9c477cf9a09325cf6a9a diff --git a/Tests/MatterControl.Tests/MatterControl/SupportGeneratorTests.cs b/Tests/MatterControl.Tests/MatterControl/SupportGeneratorTests.cs index dac318675..d80641dca 100644 --- a/Tests/MatterControl.Tests/MatterControl/SupportGeneratorTests.cs +++ b/Tests/MatterControl.Tests/MatterControl/SupportGeneratorTests.cs @@ -61,9 +61,9 @@ namespace MatterControl.Tests.MatterControl // | | // |_______| // - //______________ + // ______________ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cube = await CubeObject3D.Create(20, 20, 20); var aabb = cube.GetAxisAlignedBoundingBox(); @@ -71,8 +71,10 @@ namespace MatterControl.Tests.MatterControl cube.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb.MinXYZ.Z + 15); scene.Children.Add(cube); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 1, "We should have added some support"); foreach (var support in scene.Children.Where(i => i.OutputType == PrintOutputTypes.Support)) @@ -87,9 +89,8 @@ namespace MatterControl.Tests.MatterControl // | | // __| |__ // |_______| - // { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cube = await CubeObject3D.Create(20, 20, 20); var aabb = cube.GetAxisAlignedBoundingBox(); @@ -97,28 +98,30 @@ namespace MatterControl.Tests.MatterControl cube.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb.MinXYZ.Z - 5); scene.Children.Add(cube); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(1, scene.Children.Count, "We should not have added any support"); } // make a cube on the bed and single cube in the air and ensure that support is not generated - // _________ - // | | - // | | - // |_______| - // _________ - // | | - // | | - //___|_______|___ + // _________ + // | | + // | | + // |_______| + // _________ + // | | + // | | + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed - cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z - 5); + cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); @@ -127,8 +130,10 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 25); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } @@ -142,14 +147,13 @@ namespace MatterControl.Tests.MatterControl // | | // __| |__ // |_______| - // { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed - cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z); + cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z - 5); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); @@ -158,22 +162,24 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 25); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // make a cube on the bed and another cube exactly on top of it and ensure that support is not generated - // _________ - // | | - // | | - // |_______| - // | | - // | | - //___|_______|___ + // _________ + // | | + // | | + // |_______| + // | | + // | | + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); @@ -187,21 +193,23 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 20); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // make a cube on the bed and single cube in the air that intersects it and ensure that support is not generated - // _________ + // _________ + // | | + // |______ | // top cube actually exactly on top of bottom cube + // ||______|| // | | - // |______ | // top cube actually exactly on top of bottom cube - // ||______|| - // | | - //___|_______|___ + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); @@ -215,23 +223,25 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 15); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } - // Make a cube on the bed and single cube in the air that intersects it. + // Make a cube on the bed and single cube in the air that intersects it. // SELECT the cube on top // Ensure that support is not generated. - // _________ + // _________ + // | | + // |______ | // top cube actually exactly on top of bottom cube + // ||______|| // | | - // |______ | // top cube actually exactly on top of bottom cube - // ||______|| - // | | - //___|_______|___ + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); @@ -247,8 +257,10 @@ namespace MatterControl.Tests.MatterControl scene.SelectedItem = cubeInAir; - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } @@ -262,9 +274,9 @@ namespace MatterControl.Tests.MatterControl // | | // | | // |_______| - //_______________ + // _______________ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cube5AboveBed = await CubeObject3D.Create(20, 20, 20); var aabb5Above = cube5AboveBed.GetAxisAlignedBoundingBox(); @@ -278,8 +290,10 @@ namespace MatterControl.Tests.MatterControl cube30AboveBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb30Above.MinXYZ.Z + 30); scene.Children.Add(cube30AboveBed); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 1, "We should have added some support"); @@ -306,9 +320,9 @@ namespace MatterControl.Tests.MatterControl // | | // |_______| // - //______________ + // ______________ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cube = await CubeObject3D.Create(20, 20, 20); var aabb = cube.GetAxisAlignedBoundingBox(); @@ -316,8 +330,10 @@ namespace MatterControl.Tests.MatterControl cube.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb.MinXYZ.Z + 15); scene.Children.Add(cube); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.Normal; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.Normal + }; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 1, "We should have added some support"); foreach (var support in scene.Children.Where(i => i.OutputType == PrintOutputTypes.Support)) @@ -328,16 +344,16 @@ namespace MatterControl.Tests.MatterControl } // make a cube on the bed and single cube in the air and ensure that support is not generated - // _________ - // | | - // | | - // |_______| - // _________ - // | | - // | | - //___|_______|___ + // _________ + // | | + // | | + // |_______| + // _________ + // | | + // | | + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); @@ -351,8 +367,10 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 25); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.Normal; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.Normal + }; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 2, "We should have added some support"); foreach (var support in scene.Children.Where(i => i.OutputType == PrintOutputTypes.Support)) @@ -363,14 +381,14 @@ namespace MatterControl.Tests.MatterControl } // make a cube on the bed and single cube in the air that intersects it and ensure that support is not generated - // _________ + // _________ + // | | + // |______ | // top cube actually exactly on top of bottom cube + // ||______|| // | | - // |______ | // top cube actually exactly on top of bottom cube - // ||______|| - // | | - //___|_______|___ + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); @@ -384,22 +402,24 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 15); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.Normal; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.Normal + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // make a cube on the bed and another cube exactly on top of it and ensure that support is not generated - // _________ - // | | - // | | - // |_______| - // | | - // | | - //___|_______|___ + // _________ + // | | + // | | + // |_______| + // | | + // | | + // ___|_______|___ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); @@ -413,24 +433,26 @@ namespace MatterControl.Tests.MatterControl cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 20); scene.Children.Add(cubeInAir); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.From_Bed + }; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // Make a cube above the bed and a second above that. Ensure only one set of support material // _________ + // | | 50 // | | - // | | - // |_______| + // |_______| 30 // _________ + // | | 25 // | | - // | | - // |_______| - //_______________ + // |_______| 5 + // _______________ { - InteractiveScene scene = new InteractiveScene(); + var scene = new InteractiveScene(); var cube5AboveBed = await CubeObject3D.Create(20, 20, 20); var aabb5Above = cube5AboveBed.GetAxisAlignedBoundingBox(); @@ -444,8 +466,10 @@ namespace MatterControl.Tests.MatterControl cube30AboveBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb30Above.MinXYZ.Z + 30); scene.Children.Add(cube30AboveBed); - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.Normal; + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.Normal + }; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 2, "We should have added some support"); @@ -470,28 +494,168 @@ namespace MatterControl.Tests.MatterControl Assert.AreEqual(bedSupportCount, airSupportCount, "Same number of support columns in each space."); } + } - // load a complex part that should have no support required (not ready yet) - if(false) + [Test, Category("Support Generation")] + public async Task ComplexPartNoSupport() + { + // Set the static data to point to the directory of MatterControl + AggContext.StaticData = new FileSystemStaticData(TestContext.CurrentContext.ResolveProjectPath(4, "StaticData")); + MatterControlUtilities.OverrideAppDataLocation(TestContext.CurrentContext.ResolveProjectPath(4)); + + // load a complex part that should have no support required + var minimumSupportHeight = .05; + var scene = new InteractiveScene(); + + var meshPath = TestContext.CurrentContext.ResolveProjectPath(4, "Tests", "TestData", "TestParts", "NoSupportNeeded.stl"); + + var supportObject = new Object3D() { - InteractiveScene scene = new InteractiveScene(); + Mesh = StlProcessing.Load(meshPath, CancellationToken.None) + }; - var meshPath = TestContext.CurrentContext.ResolveProjectPath(4, "Tests", "TestData", "TestParts", "NoSupportNeeded.stl"); + var aabbCube = supportObject.GetAxisAlignedBoundingBox(); + // move it so the bottom is on the bed + supportObject.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbCube.MinXYZ.Z); + scene.Children.Add(supportObject); - var supportObject = new Object3D() + var supportGenerator = new SupportGenerator(scene, minimumSupportHeight) + { + SupportType = SupportGenerator.SupportGenerationType.Normal + }; + await supportGenerator.Create(null, CancellationToken.None); + // this test is still in progress (failing) + // Assert.AreEqual(1, scene.Children.Count, "We should not have added support"); + } + + [Test, Category("Support Generator")] + public void SupportColumnTests() + { + // we change plans into columns correctly + { + var planes = new SupportGenerator.HitPlanes(.2) { - Mesh = StlProcessing.Load(meshPath, CancellationToken.None) + new SupportGenerator.HitPlane(.178, true), + new SupportGenerator.HitPlane(10.787, true), + new SupportGenerator.HitPlane(10.787, false), + new SupportGenerator.HitPlane(13.085, true), + new SupportGenerator.HitPlane(13.085, false), + new SupportGenerator.HitPlane(15.822, false), }; - var aabbCube = supportObject.GetAxisAlignedBoundingBox(); - // move it so the bottom is on the bed - supportObject.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbCube.MinXYZ.Z); - scene.Children.Add(supportObject); + var column0 = new SupportGenerator.SupportColumn(planes, .2); + Assert.AreEqual(1, column0.Count); + Assert.AreEqual((10.787, 13.085), column0[0]); + } - var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); - supportGenerator.SupportType = SupportGenerator.SupportGenerationType.Normal; - await supportGenerator.Create(null, CancellationToken.None); - Assert.AreEqual(1, scene.Children.Count, "We should not have added support"); + // 0 no data so copy of 1 + { + var column0 = new SupportGenerator.SupportColumn(.1); + + var column1 = new SupportGenerator.SupportColumn(.1) + { + (0, 5), + (25, 30) + }; + + column0.Union(column1); + Assert.AreEqual(2, column0.Count); + Assert.AreEqual((0, 5), column0[0]); + Assert.AreEqual((25, 30), column0[1]); + } + + // 0 data 1 no data + { + var column0 = new SupportGenerator.SupportColumn(.1) + { + (0, 5), + (25, 30) + }; + + var column1 = new SupportGenerator.SupportColumn(.1); + + column0.Union(column1); + Assert.AreEqual(2, column0.Count); + Assert.AreEqual(0, column0[0].start); + Assert.AreEqual(5, column0[0].end); + Assert.AreEqual(25, column0[1].start); + Assert.AreEqual(30, column0[1].end); + } + + // 0 and 1 have same data + { + var column0 = new SupportGenerator.SupportColumn(.1) + { + (0, 5), + (25, 30) + }; + + var column1 = new SupportGenerator.SupportColumn(.1) + { + (0, 5), + (25, 30) + }; + + column0.Union(column1); + Assert.AreEqual(2, column0.Count); + Assert.AreEqual((0, 5), column0[0]); + Assert.AreEqual((25, 30), column0[1]); + } + + // 1 makes 0 have one run + { + var column0 = new SupportGenerator.SupportColumn(.1) + { + (0, 5), + (25, 30) + }; + + var column1 = new SupportGenerator.SupportColumn(.1) + { + (5, 25) + }; + + column0.Union(column1); + Assert.AreEqual(1, column0.Count); + Assert.AreEqual((0, 30), column0[0]); + } + + // 1 makes 0 have 3 runs + { + var column0 = new SupportGenerator.SupportColumn(.1) + { + (0, 5), + (25, 30) + }; + + var column1 = new SupportGenerator.SupportColumn(.1) + { + (6, 24) + }; + + column0.Union(column1); + Assert.AreEqual(3, column0.Count); + Assert.AreEqual((0, 5), column0[0]); + Assert.AreEqual((6, 24), column0[1]); + Assert.AreEqual((25, 30), column0[2]); + } + + // 1 makes 0 have one run considering overlap + { + var column0 = new SupportGenerator.SupportColumn(2) + { + (0, 5), + (25, 30) + }; + + var column1 = new SupportGenerator.SupportColumn(2) + { + (6, 24) + }; + + column0.Union(column1); + Assert.AreEqual(1, column0.Count); + Assert.AreEqual((0, 30), column0[0]); } } @@ -500,91 +664,550 @@ namespace MatterControl.Tests.MatterControl { // a box in the air { - var planes = new List<(double z, bool bottom)>() + var planes = new SupportGenerator.HitPlanes(0) { - (0, false), // top at 0 (the bed) - (5, true), // bottom at 5 (the bottom of a box) - (10, false), // top at 10 (the top of the box) + new SupportGenerator.HitPlane(0, false), // top at 0 (the bed) + new SupportGenerator.HitPlane(5, true), // bottom at 5 (the bottom of a box) + new SupportGenerator.HitPlane(10, false), // top at 10 (the top of the box) }; - int bottom = SupportGenerator.GetNextBottom(0, planes, 0); + int bottom = planes.GetNextBottom(0); Assert.AreEqual(1, bottom); // we get the bottom - int bottom1 = SupportGenerator.GetNextBottom(1, planes, 0); + int bottom1 = planes.GetNextBottom(1); Assert.AreEqual(-1, bottom1, "There are no more bottoms so we get back a -1."); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(1, supports.Count); + Assert.AreEqual(0, supports[0].start); + Assert.AreEqual(5, supports[0].end); } // two boxes, the bottom touching the bed, the top touching the bottom { - var planes = new List<(double z, bool bottom)>() + var planes = new SupportGenerator.HitPlanes(0) { - (0, false), // top at 0 (the bed) - (0, true), // bottom at 0 (box a on bed) - (10, false), // top at 10 (box a top) - (10, true), // bottom at 10 (box b bottom) - (20, false) // top at 20 (box b top) + new SupportGenerator.HitPlane(0, false), // top at 0 (the bed) + new SupportGenerator.HitPlane(0, true), // bottom at 0 (box a on bed) + new SupportGenerator.HitPlane(10, false), // top at 10 (box a top) + new SupportGenerator.HitPlane(10, true), // bottom at 10 (box b bottom) + new SupportGenerator.HitPlane(20, false) // top at 20 (box b top) }; - int bottom = SupportGenerator.GetNextBottom(0, planes, 0); + int bottom = planes.GetNextBottom(0); Assert.AreEqual(-1, bottom, "The boxes are sitting on the bed and no support is required"); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(0, supports.Count); } // two boxes, the bottom touching the bed, the top inside the bottom { - var planes = new List<(double z, bool bottom)>() + var planes = new SupportGenerator.HitPlanes(0) { - (0, false), // top at 0 (the bed) - (0, true), // bottom at 0 (box a on bed) - (5, true), // bottom at 5 (box b bottom) - (10, false), // top at 10 (box a top) - (20, false) // top at 20 (box b top) + new SupportGenerator.HitPlane(0, false), // top at 0 (the bed) + new SupportGenerator.HitPlane(0, true), // bottom at 0 (box a on bed) + new SupportGenerator.HitPlane(5, true), // bottom at 5 (box b bottom) + new SupportGenerator.HitPlane(10, false), // top at 10 (box a top) + new SupportGenerator.HitPlane(20, false) // top at 20 (box b top) }; - int bottom = SupportGenerator.GetNextBottom(0, planes, 0); + int bottom = planes.GetNextBottom(0); Assert.AreEqual(-1, bottom, "The boxes are sitting on the bed and no support is required"); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(0, supports.Count); } // get next top skips any tops before checking for bottom { - var planes = new List<(double z, bool bottom)>() + var planes = new SupportGenerator.HitPlanes(0) { - (0, false), - (5, true), - (10, false), - (20, false), - (25, true) + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(5, true), + new SupportGenerator.HitPlane(10, false), + new SupportGenerator.HitPlane(20, false), + new SupportGenerator.HitPlane(25, true) }; - int top = SupportGenerator.GetNextTop(0, planes, 0); + int top = planes.GetNextTop(0); Assert.AreEqual(3, top); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(2, supports.Count); + Assert.AreEqual(0, supports[0].start); + Assert.AreEqual(5, supports[0].end); + Assert.AreEqual(20, supports[1].start); + Assert.AreEqual(25, supports[1].end); } // actual output from a dual extrusion print that should have no support { - var planes = new List<(double z, bool bottom)>() + var planes = new SupportGenerator.HitPlanes(.1) { - (0, true), - (0, true), - (0, true), - (0, true), - (0, false), - (0.0302, true), - (0.0497, true), - (0.762, true), - (0.762, true), - (0.762, false), - (0.762, false), - (15.95, false), - (15.9697, false), - (16, false), - (16, false), - (16, false), - (16, false), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(0.0302, true), + new SupportGenerator.HitPlane(0.0497, true), + new SupportGenerator.HitPlane(0.762, true), + new SupportGenerator.HitPlane(0.762, true), + new SupportGenerator.HitPlane(0.762, false), + new SupportGenerator.HitPlane(0.762, false), + new SupportGenerator.HitPlane(15.95, false), + new SupportGenerator.HitPlane(15.9697, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), }; - int bottom = SupportGenerator.GetNextBottom(0, planes, .1); + int bottom = planes.GetNextBottom(0); Assert.AreEqual(-1, bottom, "The boxes are sitting on the bed and no support is required"); } + + // make sure we have a valid range even when there is no top + { + var planes = new SupportGenerator.HitPlanes(.1) + { + // top at 0 + new SupportGenerator.HitPlane(0, false), + // area needing support + new SupportGenerator.HitPlane(20, true), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(20, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(20.1, planes[1].Z); + } + + // make sure we user last support height if greater than first plus min distance + { + var planes = new SupportGenerator.HitPlanes(.1) + { + // top at 0 + new SupportGenerator.HitPlane(0, false), + // area needing support + new SupportGenerator.HitPlane(20, true), + new SupportGenerator.HitPlane(22, true), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(20, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(22.1, planes[1].Z); + } + + // make sure we remove extra bottoms and have a valid range even when there is no top + { + var planes = new SupportGenerator.HitPlanes(.1) + { + // top at 0 + new SupportGenerator.HitPlane(0, false), + // area needing support + new SupportGenerator.HitPlane(20, true), + new SupportGenerator.HitPlane(20, true), + new SupportGenerator.HitPlane(20.001, true), + new SupportGenerator.HitPlane(20.002, true), + new SupportGenerator.HitPlane(20.003, true), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(20, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(20.103, planes[1].Z); + } + + // simple gap + { + var planes = new SupportGenerator.HitPlanes(.1) + { + // area needing support + new SupportGenerator.HitPlane(20, true), + // bad extra top + new SupportGenerator.HitPlane(22, false), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(20, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(22, planes[1].Z); + } + + // many start top planes + { + var planes = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(1, false), + new SupportGenerator.HitPlane(2, false), + new SupportGenerator.HitPlane(3, false), + // area needing support + new SupportGenerator.HitPlane(20, true), + // bad extra top + new SupportGenerator.HitPlane(22, true), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(20, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(22.1, planes[1].Z); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(1, supports.Count); + Assert.AreEqual((0, 20), supports[0]); + } + + // handle invalid date (can happen during the trace in edge cases) + { + var planes = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), + // area needing support + new SupportGenerator.HitPlane(20, false), + // bad extra top + new SupportGenerator.HitPlane(22, false), + }; + + planes.Simplify(); + Assert.AreEqual(0, planes.Count); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(0, supports.Count); + } + + // handle invalid date (can happen during the trace in edge cases) + { + var planes = new SupportGenerator.HitPlanes(.1) + { + // bottom at 0 + new SupportGenerator.HitPlane(0, true), + // bottom at 20 + new SupportGenerator.HitPlane(20, true), + // bottom at 22 + new SupportGenerator.HitPlane(22, true), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(0, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(22.1, planes[1].Z); + } + + // simplify working as expected (planes with space turns into two start end sets) + { + var planes = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(0.0302, true), + new SupportGenerator.HitPlane(0.0497, true), + new SupportGenerator.HitPlane(0.762, true), + new SupportGenerator.HitPlane(0.762, true), + new SupportGenerator.HitPlane(0.762, false), + new SupportGenerator.HitPlane(0.762, false), + new SupportGenerator.HitPlane(15.95, false), + new SupportGenerator.HitPlane(15.9697, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + // area needing support + new SupportGenerator.HitPlane(20, true), + new SupportGenerator.HitPlane(25, false), + }; + + planes.Simplify(); + Assert.AreEqual(4, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(0, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(16, planes[1].Z); + Assert.IsTrue(planes[2].Bottom()); + Assert.AreEqual(20, planes[2].Z); + Assert.IsTrue(planes[3].Top()); + Assert.AreEqual(25, planes[3].Z); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(1, supports.Count); + Assert.AreEqual(16, supports[0].start); + Assert.AreEqual(20, supports[0].end); + } + + // pile of plates turns into 0 start end + { + var planes = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, true), + new SupportGenerator.HitPlane(0, false), + new SupportGenerator.HitPlane(0.0302, true), + new SupportGenerator.HitPlane(0.0497, true), + new SupportGenerator.HitPlane(0.762, true), + new SupportGenerator.HitPlane(0.762, true), + new SupportGenerator.HitPlane(0.762, false), + new SupportGenerator.HitPlane(0.762, false), + new SupportGenerator.HitPlane(15.95, false), + new SupportGenerator.HitPlane(15.9697, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + new SupportGenerator.HitPlane(16, false), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(0, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(16, planes[1].Z); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(0, supports.Count); + } + + // a test with an actual part starting below the bed + { + var planes = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(-.9966, false), + new SupportGenerator.HitPlane(-.9965, true), + new SupportGenerator.HitPlane(-.9964, false), + new SupportGenerator.HitPlane(-.9963, true), + new SupportGenerator.HitPlane(-.9962, false), + new SupportGenerator.HitPlane(-.9961, true), // last plane below bed is a top + new SupportGenerator.HitPlane(13.48, true), + new SupportGenerator.HitPlane(13.48, false), + new SupportGenerator.HitPlane(14.242, false), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(0, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(14.242, planes[1].Z); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(0, supports.Count); + } + + // a test with an actual part starting below the bed + { + var planes = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(-.9961, true), + new SupportGenerator.HitPlane(-.9962, false), + new SupportGenerator.HitPlane(-.9963, true), + new SupportGenerator.HitPlane(-.9964, false), + new SupportGenerator.HitPlane(-.9965, true), + new SupportGenerator.HitPlane(-.9966, true), // last plane below bed is a bottom (no support needed) + new SupportGenerator.HitPlane(13.48, true), + new SupportGenerator.HitPlane(13.48, false), + new SupportGenerator.HitPlane(14.242, false), + }; + + planes.Simplify(); + Assert.AreEqual(2, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(0, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(14.242, planes[1].Z); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(0, supports.Count); + } + + { + var planes = new SupportGenerator.HitPlanes(.1) + { + // top at 0 + new SupportGenerator.HitPlane(0, false), + // bottom at 5 + new SupportGenerator.HitPlane(5, true), + new SupportGenerator.HitPlane(5, true), + new SupportGenerator.HitPlane(5, true), + new SupportGenerator.HitPlane(5, true), + new SupportGenerator.HitPlane(5, true), + new SupportGenerator.HitPlane(5, true), + // top at 25 + new SupportGenerator.HitPlane(25, false), + new SupportGenerator.HitPlane(25, false), + new SupportGenerator.HitPlane(25, false), + new SupportGenerator.HitPlane(25, false), + new SupportGenerator.HitPlane(25, false), + new SupportGenerator.HitPlane(25, false), + // bottom at 30 + new SupportGenerator.HitPlane(30, true), + new SupportGenerator.HitPlane(30, true), + new SupportGenerator.HitPlane(30, true), + new SupportGenerator.HitPlane(30, true), + new SupportGenerator.HitPlane(30, true), + new SupportGenerator.HitPlane(30, true), + // top at 50 + new SupportGenerator.HitPlane(50, false), + new SupportGenerator.HitPlane(50, false), + new SupportGenerator.HitPlane(50, false), + new SupportGenerator.HitPlane(50, false), + new SupportGenerator.HitPlane(50, false), + }; + + planes.Simplify(); + Assert.AreEqual(4, planes.Count); + Assert.IsTrue(planes[0].Bottom()); + Assert.AreEqual(5, planes[0].Z); + Assert.IsTrue(planes[1].Top()); + Assert.AreEqual(25, planes[1].Z); + Assert.IsTrue(planes[2].Bottom()); + Assert.AreEqual(30, planes[2].Z); + Assert.IsTrue(planes[3].Top()); + Assert.AreEqual(50, planes[3].Z); + + var supports = new SupportGenerator.SupportColumn(planes, 0); + Assert.AreEqual(2, supports.Count); + Assert.AreEqual(0, supports[0].start); + Assert.AreEqual(5, supports[0].end); + Assert.AreEqual(25, supports[1].start); + Assert.AreEqual(30, supports[1].end); + } + + { + // two parts with a support gap between them + var planes0 = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(0, true), // bottom of part + new SupportGenerator.HitPlane(15, false), // top of part + }; + var support0 = new SupportGenerator.SupportColumn(planes0, .1); + Assert.AreEqual(0, support0.Count); + + // a part that will fill the support gap + var planes1 = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(20, true), // bottom of part + new SupportGenerator.HitPlane(30, false), // top of part + }; + var support1 = new SupportGenerator.SupportColumn(planes1, .1); + Assert.AreEqual(1, support1.Count); + Assert.AreEqual(0, support1[0].start); + Assert.AreEqual(20, support1[0].end); + + support0.Union(support1); + Assert.AreEqual(1, support0.Count); + Assert.AreEqual(0, support0[0].start); + Assert.AreEqual(20, support0[0].end); + } + + // merge of two overlapping sets tuns into 0 set + { + // two parts with a support gap between them + var planes0 = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(10, true), // bottom of part + new SupportGenerator.HitPlane(20, false), // top of part + }; + + planes0.Simplify(); + Assert.AreEqual(2, planes0.Count); + Assert.IsTrue(planes0[0].Bottom()); + Assert.AreEqual(10, planes0[0].Z); + Assert.IsTrue(planes0[1].Top()); + Assert.AreEqual(20, planes0[1].Z); + + var support0 = new SupportGenerator.SupportColumn(planes0, .1); + + // a part that will fill the support gap + var planes1 = new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(0, true), // bottom of part + new SupportGenerator.HitPlane(15, false), // top of part + }; + + planes1.Simplify(); + Assert.AreEqual(2, planes1.Count); + Assert.IsTrue(planes1[0].Bottom()); + Assert.AreEqual(0, planes1[0].Z); + Assert.IsTrue(planes1[1].Top()); + Assert.AreEqual(15, planes1[1].Z); + + var support1 = new SupportGenerator.SupportColumn(planes1, .1); + + support0.Union(support1); + Assert.AreEqual(1, support0.Count); + Assert.AreEqual(0, support0[0].start); + Assert.AreEqual(10, support0[0].end); + } + + { + // two parts with a support gap between them + var support0 = new SupportGenerator.SupportColumn(new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(10, true), // bottom of part + new SupportGenerator.HitPlane(20, false), // top of part + }, .1); + + // a part that will fill the support gap + var support1 = new SupportGenerator.SupportColumn(new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(0, true), // bottom of part + new SupportGenerator.HitPlane(15, false), // top of part + }, .1); + + support0.Union(support1); + Assert.AreEqual(1, support0.Count); + Assert.AreEqual(0, support0[0].start); + Assert.AreEqual(10, support0[0].end); + } + + { + // two parts with a support gap between them + var support0 = new SupportGenerator.SupportColumn(new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(10, true), // bottom of part + new SupportGenerator.HitPlane(20, false), // top of part + }, .1); + + // a part that will fill the support gap + var support1 = new SupportGenerator.SupportColumn(new SupportGenerator.HitPlanes(.1) + { + new SupportGenerator.HitPlane(0, false), // bed + new SupportGenerator.HitPlane(0, true), // bottom of part + new SupportGenerator.HitPlane(15, false), // top of part + }, .1); + + support1.Union(support0); + Assert.AreEqual(1, support1.Count); + Assert.AreEqual(0, support1[0].start); + Assert.AreEqual(10, support1[0].end); + } } } -} +} \ No newline at end of file