From 5f0cacf4c72fd64c6bbff1b24a01667e61bb778d Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Wed, 23 Jan 2019 16:08:27 -0800 Subject: [PATCH] Support has first release quality Made 'support type' work selectable --- .../DesignTools/PublicPropertyEditor.cs | 23 +- .../DesignTools/SupportGenerator.cs | 460 +++++++++--------- .../PartPreviewWindow/GenerateSupportPanel.cs | 28 +- .../SettingsManagement/UserSettings.cs | 1 + 4 files changed, 269 insertions(+), 243 deletions(-) diff --git a/MatterControlLib/DesignTools/PublicPropertyEditor.cs b/MatterControlLib/DesignTools/PublicPropertyEditor.cs index 215075908..527924c0f 100644 --- a/MatterControlLib/DesignTools/PublicPropertyEditor.cs +++ b/MatterControlLib/DesignTools/PublicPropertyEditor.cs @@ -308,18 +308,27 @@ namespace MatterHackers.MatterControl.DesignTools } //field.Content - undoBuffer.AddAndDo(new UndoRedoActions(() => + if (undoBuffer != null) { - property.SetValue(valueFromString(oldValue)); - object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer)); - propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - }, - () => + undoBuffer.AddAndDo(new UndoRedoActions(() => + { + property.SetValue(valueFromString(oldValue)); + object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + }, + () => + { + property.SetValue(valueFromString(newValue)); + object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer)); + propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); + })); + } + else { property.SetValue(valueFromString(newValue)); object3D?.Invalidate(new InvalidateArgs(context.item, InvalidateType.Properties, undoBuffer)); propertyGridModifier?.UpdateControls(new PublicPropertyChange(context, property.PropertyInfo.Name)); - })); + } }; } diff --git a/MatterControlLib/DesignTools/SupportGenerator.cs b/MatterControlLib/DesignTools/SupportGenerator.cs index 861ecc654..a280be52d 100644 --- a/MatterControlLib/DesignTools/SupportGenerator.cs +++ b/MatterControlLib/DesignTools/SupportGenerator.cs @@ -34,8 +34,6 @@ using System.Threading; using System.Threading.Tasks; using MatterHackers.Agg; using MatterHackers.Agg.Image; -using MatterHackers.Agg.Transform; -using MatterHackers.Agg.VertexSource; using MatterHackers.DataConverters3D; using MatterHackers.PolygonMesh; using MatterHackers.RayTracer; @@ -88,6 +86,8 @@ namespace MatterHackers.MatterControl.DesignTools this.scene = scene; } + public enum SupportGenerationType { Normal, From_Bed } + public double MaxOverHangAngle { get @@ -133,30 +133,30 @@ namespace MatterHackers.MatterControl.DesignTools } } + public SupportGenerationType SupportType + { + get + { + var supportString = UserSettings.Instance.get(UserSettingsKey.SupportGenerationType); + if (Enum.TryParse(supportString, out SupportGenerationType supportType)) + { + return supportType; + } + + return SupportGenerationType.Normal; + } + + set + { + UserSettings.Instance.set(UserSettingsKey.SupportGenerationType, value.ToString()); + } + } + /// /// The amount to reduce the pillars so they are separated in the 3D view /// private double reduceAmount => .99; - // function to get all the columns that need support generation - 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); - } - } - } - } - public Task Create(IProgress progress, CancellationToken cancelationToken) { ProgressStatus status = new ProgressStatus(); @@ -177,8 +177,7 @@ namespace MatterHackers.MatterControl.DesignTools AxisAlignedBoundingBox allBounds = AxisAlignedBoundingBox.Empty(); foreach (var candidate in supportCandidates) { - var matrix = candidate.WorldMatrix(scene); - allBounds += candidate.GetAxisAlignedBoundingBox(); + allBounds += candidate.GetAxisAlignedBoundingBox(candidate.Matrix.Inverted * candidate.WorldMatrix()); } // create the gird of possible support @@ -223,204 +222,6 @@ namespace MatterHackers.MatterControl.DesignTools return Task.CompletedTask; } - int GetNextTop(int i, List<(double z, bool bottom)> planes) - { - while (i < planes.Count - && planes[i].bottom) - { - i++; - } - - return i; - } - - int GetNextBottom(int i, List<(double z, bool bottom)> planes) - { - // first skip all the tops - while (i < planes.Count - && !planes[i].bottom) - { - i++; - } - - // then look for the last bottom before next top - while (i < planes.Count - && planes[i].bottom - && planes.Count > i + 1 - && planes[i + 1].bottom) - { - i++; - } - - return i; - } - - private void AddSupportColumns(RectangleDouble gridBounds, Dictionary<(int x, int y), List<(double z, bool bottom)>> detectedPlanes) - { - IObject3D supportColumnsToAdd = new Object3D(); - bool fromBed = false; - foreach (var kvp in detectedPlanes) - { - if(kvp.Value.Count == 0) - { - continue; - } - - int i = 0; - - kvp.Value.Sort((a, b) => - { - return a.z.CompareTo(b.z); - }); - - var yPos = (gridBounds.Bottom + kvp.Key.y) * PillarSize; - var xPos = (gridBounds.Left + kvp.Key.x) * PillarSize; - - if (fromBed) - { - i = GetNextBottom(i, kvp.Value); - if (kvp.Value[i].bottom) - { - AddSupportColumn(supportColumnsToAdd, xPos, yPos, 0, kvp.Value[i].z); - } - } - else - { - double lastTopZ = 0; - int lastBottom = i; - do - { - // if the first plane is a top, move to the last top before we find a bottom - if(i == 0 - && !kvp.Value[i].bottom) - { - i = GetNextTop(i + 1, kvp.Value); - if (i < kvp.Value.Count) - { - lastTopZ = kvp.Value[i].z; - } - } - lastBottom = i; - // find all open arreas in the list and add support - i = GetNextBottom(i, kvp.Value); - if (i < kvp.Value.Count - && kvp.Value[i].bottom) - { - AddSupportColumn(supportColumnsToAdd, xPos, yPos, lastTopZ, kvp.Value[i].z); - } - i = GetNextTop(i+1, kvp.Value); - if (i < kvp.Value.Count) - { - lastTopZ = kvp.Value[i].z; - } - } while (i != lastBottom && i < kvp.Value.Count); - } - } - - scene.Children.Modify(list => - { - list.AddRange(supportColumnsToAdd.Children); - }); - } - - private Dictionary<(int x, int y), List<(double z, bool bottom)>> DetectRequiredSupportByTracing(RectangleDouble gridBounds, IEnumerable supportCandidates) - { - 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)>>(); - - int gridWidth = (int)gridBounds.Width; - int gridHeight = (int)gridBounds.Height; - - // 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++) - { - IntersectInfo upHit = null; - - for (double yOffset = -1; yOffset <= 1; yOffset++) - { - for (double xOffset = -1; xOffset <= 1; xOffset++) - { - var yPos = (gridBounds.Bottom + y) * PillarSize + (yOffset * PillarSize / 2); - var xPos = (gridBounds.Left + x) * PillarSize + (xOffset * PillarSize / 2); - - var upRay = new Ray(new Vector3(xPos + .000013, yPos - .00027, 0), Vector3.UnitZ, intersectionType: IntersectionType.Both); - do - { - upHit = traceData.GetClosestIntersection(upRay); - if (upHit != null) - { - if (!detectedPlanes.ContainsKey((x, y))) - { - detectedPlanes.Add((x, y), new List<(double z, bool bottom)>()); - } - - detectedPlanes[(x, y)].Add((upHit.HitPosition.Z, upHit.normalAtHit.Z < 0)); - - // 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.Both); - } - } while (upHit != null); - } - } - } - } - - return detectedPlanes; - } - - private IPrimitive GetTraceData(IEnumerable supportCandidates) - { - List supportVerts; - FaceList supportFaces; - - // find all the faces that are candidates for support - supportVerts = new List(); - supportFaces = new FaceList(); - foreach (var item in supportCandidates) - { - // add all the down faces to supportNeededImage - 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 angle = MathHelper.RadiansToDegrees(Math.Acos(face0Normal.Dot(-Vector3Float.UnitZ))); - - // check if the face is pointing in the up direction at all - bool isUpFace = angle > 90; - - // check if the face is pointing down - - if (angle < MaxOverHangAngle - || isUpFace) - { - 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); - } - } - - // add all mesh edges that need support - - // add all unsupported vertices (low points of a face group that individually do not need support) - } - - return supportFaces.CreateTraceData(supportVerts); - } - - public Agg.UI.HAnchor SupportType { get; set; } = Agg.UI.HAnchor.Fit; - public void RemoveExisting() { var existingSupports = scene.Children.Where(i => i.GetType() == typeof(GeneratedSupportObject3D)); @@ -493,5 +294,222 @@ namespace MatterHackers.MatterControl.DesignTools holder.Children.Add(support); } + + private void AddSupportColumns(RectangleDouble gridBounds, Dictionary<(int x, int y), List<(double z, bool bottom)>> detectedPlanes) + { + IObject3D supportColumnsToAdd = new Object3D(); + bool fromBed = SupportType == SupportGenerationType.From_Bed; + var halfPillar = PillarSize / 2; + foreach (var kvp in detectedPlanes) + { + if (kvp.Value.Count == 0) + { + continue; + } + + int i = 0; + + kvp.Value.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) + { + i = GetNextBottom(i, kvp.Value); + if (kvp.Value[i].bottom) + { + AddSupportColumn(supportColumnsToAdd, xPos, yPos, 0, kvp.Value[i].z); + } + } + else + { + double lastTopZ = 0; + int lastBottom = i; + do + { + // if the first plane is a top, move to the last top before we find a bottom + if (i == 0 + && !kvp.Value[i].bottom) + { + i = GetNextTop(i + 1, kvp.Value); + if (i < kvp.Value.Count) + { + lastTopZ = kvp.Value[i].z; + } + } + lastBottom = i; + // find all open arreas in the list and add support + i = GetNextBottom(i, kvp.Value); + if (i < kvp.Value.Count + && kvp.Value[i].bottom) + { + AddSupportColumn(supportColumnsToAdd, xPos, yPos, lastTopZ, kvp.Value[i].z); + } + i = GetNextTop(i + 1, kvp.Value); + if (i < kvp.Value.Count) + { + lastTopZ = kvp.Value[i].z; + } + } while (i != lastBottom && i < kvp.Value.Count); + } + } + + scene.Children.Modify(list => + { + list.AddRange(supportColumnsToAdd.Children); + }); + } + + private Dictionary<(int x, int y), List<(double z, bool bottom)>> DetectRequiredSupportByTracing(RectangleDouble gridBounds, IEnumerable supportCandidates) + { + 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)>>(); + + int gridWidth = (int)gridBounds.Width; + int gridHeight = (int)gridBounds.Height; + + // 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++) + { + IntersectInfo upHit = null; + + for (double yOffset = -1; yOffset <= 1; yOffset++) + { + for (double xOffset = -1; xOffset <= 1; xOffset++) + { + var halfPillar = PillarSize / 2; + var yPos = (gridBounds.Bottom + y) * PillarSize + halfPillar + (yOffset * halfPillar); + var xPos = (gridBounds.Left + x) * PillarSize + halfPillar + (xOffset * halfPillar); + + var upRay = new Ray(new Vector3(xPos + .000013, yPos - .00027, 0), Vector3.UnitZ, intersectionType: IntersectionType.Both); + do + { + upHit = traceData.GetClosestIntersection(upRay); + if (upHit != null) + { + if (!detectedPlanes.ContainsKey((x, y))) + { + detectedPlanes.Add((x, y), new List<(double z, bool bottom)>()); + } + + detectedPlanes[(x, y)].Add((upHit.HitPosition.Z, upHit.normalAtHit.Z < 0)); + + // 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.Both); + } + } while (upHit != null); + } + } + } + } + + return detectedPlanes; + } + + private int GetNextBottom(int i, List<(double z, bool bottom)> planes) + { + // first skip all the tops + while (i < planes.Count + && !planes[i].bottom) + { + i++; + } + + // then look for the last bottom before next top + while (i < planes.Count + && planes[i].bottom + && planes.Count > i + 1 + && planes[i + 1].bottom) + { + i++; + } + + return i; + } + + private int GetNextTop(int i, List<(double z, bool bottom)> planes) + { + while (i < planes.Count + && planes[i].bottom) + { + i++; + } + + return i; + } + + // 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); + } + } + } + } + + private IPrimitive GetTraceData(IEnumerable supportCandidates) + { + List supportVerts; + FaceList supportFaces; + + // find all the faces that are candidates for support + supportVerts = new List(); + supportFaces = new FaceList(); + foreach (var item in supportCandidates) + { + // add all the down faces to supportNeededImage + 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 angle = MathHelper.RadiansToDegrees(Math.Acos(face0Normal.Dot(-Vector3Float.UnitZ))); + + // check if the face is pointing in the up direction at all + bool isUpFace = angle > 90; + + // check if the face is pointing down + + if (angle < MaxOverHangAngle + || isUpFace) + { + 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); + } + } + + // add all mesh edges that need support + + // add all unsupported vertices (low points of a face group that individually do not need support) + } + + return supportFaces.CreateTraceData(supportVerts); + } } } \ No newline at end of file diff --git a/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs b/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs index 745541421..6fd1c9ef1 100644 --- a/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs +++ b/MatterControlLib/PartPreviewWindow/GenerateSupportPanel.cs @@ -61,9 +61,20 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.BackgroundColor = theme.BackgroundColor; this.Padding = theme.DefaultContainerPadding; - // put in support pillar size + // Add an editor field for the SupportGenerator.SupportType + PropertyInfo propertyInfo = typeof(SupportGenerator).GetProperty(nameof(SupportGenerator.SupportType)); - // support pillar resolution + var editor = PublicPropertyEditor.CreatePropertyEditor( + new EditableProperty(propertyInfo, supportGenerator), + null, + new PPEContext(), + theme); + if (editor != null) + { + this.AddChild(editor); + } + + // put in support pillar size var pillarSizeField = new DoubleField(theme); pillarSizeField.Initialize(0); pillarSizeField.DoubleValue = supportGenerator.PillarSize; @@ -99,19 +110,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow overHangRow.AddChild(overHangField.Content); this.AddChild(overHangRow); - // Add an editor field for the SupportGenerator.SupportType - PropertyInfo propertyInfo = typeof(SupportGenerator).GetProperty(nameof(SupportGenerator.SupportType)); - - var editor = PublicPropertyEditor.CreatePropertyEditor( - new EditableProperty(propertyInfo, supportGenerator), - null, - new PPEContext(), - theme); - if (editor != null) - { - this.AddChild(editor); - } - // Button Row var buttonRow = new FlowLayoutWidget() { diff --git a/MatterControlLib/SettingsManagement/UserSettings.cs b/MatterControlLib/SettingsManagement/UserSettings.cs index 52e9a31d0..69d680726 100644 --- a/MatterControlLib/SettingsManagement/UserSettings.cs +++ b/MatterControlLib/SettingsManagement/UserSettings.cs @@ -44,6 +44,7 @@ namespace MatterHackers.MatterControl public const string NotificationEmailAddress = nameof(NotificationEmailAddress); public const string NotificationPhoneNumber = nameof(NotificationPhoneNumber); public const string OpenScadPath = nameof(OpenScadPath); + public const string SupportGenerationType = nameof(SupportGenerationType); public const string SupportMaxOverHangAngle = nameof(SupportMaxOverHangAngle); public const string SupportPillarSize = nameof(SupportPillarSize); public const string PopupLibraryWidth = nameof(PopupLibraryWidth);