From 60f5d06742df073c58bcb6cece019b110d2333e1 Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Fri, 17 Jun 2022 10:51:05 -0700 Subject: [PATCH 1/5] Adding a send g-code object --- .../DesignTools/Interfaces/IEditorDraw.cs | 9 +- .../Primitives/SendGCodeObject3D.cs | 209 ++++++++++++++++++ .../Primitives/SetTemperatureObject3D.cs | 7 +- .../CalibrationPartsContainer.cs | 9 +- .../DesignAppsCollectionContainer.cs | 10 + .../MatterControl/ScriptingPartsContainer.cs | 124 +++++++++++ .../View3D/Object3DControlsLayer.cs | 2 +- StaticData/Icons/Library/scripting_icon.png | Bin 0 -> 6677 bytes .../11842437371347377518-256x256.png | Bin 0 -> 21244 bytes StaticData/Translations/Master.txt | 9 + Submodules/MatterSlice | 2 +- Submodules/agg-sharp | 2 +- 12 files changed, 368 insertions(+), 15 deletions(-) create mode 100644 MatterControlLib/DesignTools/Primitives/SendGCodeObject3D.cs create mode 100644 MatterControlLib/Library/Providers/MatterControl/ScriptingPartsContainer.cs create mode 100644 StaticData/Icons/Library/scripting_icon.png create mode 100644 StaticData/Images/Thumbnails/11842437371347377518-256x256.png diff --git a/MatterControlLib/DesignTools/Interfaces/IEditorDraw.cs b/MatterControlLib/DesignTools/Interfaces/IEditorDraw.cs index ee97cf9c8..85d6f6e8f 100644 --- a/MatterControlLib/DesignTools/Interfaces/IEditorDraw.cs +++ b/MatterControlLib/DesignTools/Interfaces/IEditorDraw.cs @@ -42,10 +42,13 @@ namespace MatterHackers.MatterControl.DesignTools AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer); } - public interface ICustomEditorDraw : IEditorDraw + public interface IEditorDrawControled : IEditorDraw + { + bool DoEditorDraw(bool isSelected); + } + + public interface ICustomEditorDraw : IEditorDrawControled { void AddEditorTransparents(Object3DControlsLayer object3DControlLayer, List transparentMeshes, DrawEventArgs e); - - bool DoEditorDraw(bool isSelected); } } \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Primitives/SendGCodeObject3D.cs b/MatterControlLib/DesignTools/Primitives/SendGCodeObject3D.cs new file mode 100644 index 000000000..84224db1e --- /dev/null +++ b/MatterControlLib/DesignTools/Primitives/SendGCodeObject3D.cs @@ -0,0 +1,209 @@ +/* +Copyright (c) 2019, Lars Brubaker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System.Collections.Generic; +using System.Threading.Tasks; +using MatterControl.Printing; +using MatterHackers.Agg; +using MatterHackers.Agg.Font; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.PolygonMesh; +using MatterHackers.PolygonMesh.Processors; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class SendGCodeObject3D : Object3D, IObject3DControlsProvider, IGCodeTransformer, IEditorDrawControled + { + private bool hasBeenReached; + private double accumulatedLayerHeight; + + public SendGCodeObject3D() + { + Name = "Send G-Code".Localize(); + Color = Color.White.WithAlpha(.4); + Mesh = new RoundedRect(-20, -20, 20, 20, 3) + { + ResolutionScale = 10 + }.Extrude(.2); + } + + public static async Task Create() + { + var item = new SendGCodeObject3D(); + await item.Rebuild(); + return item; + } + + public string GCodeToSend { get; set; } = ""; + + public override bool Printable => false; + + public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer) + { + object3DControlsLayer.AddControls(ControlTypes.MoveInZ | ControlTypes.Shadow | ControlTypes.ScaleMatrixXY); + } + + public override async void OnInvalidate(InvalidateArgs invalidateType) + { + if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties) + && invalidateType.Source == this) + { + await Rebuild(); + } + else + { + base.OnInvalidate(invalidateType); + } + } + + public override Task Rebuild() + { + this.DebugDepth("Rebuild"); + + using (RebuildLock()) + { + using (new CenterAndHeightMaintainer(this)) + { + } + } + + Invalidate(InvalidateType.DisplayValues); + + UpdateTexture(); + + this.CancelAllParentBuilding(); + Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Mesh)); + return Task.CompletedTask; + } + + private (string gcode, double worldZ) displayInfo = ("", double.MinValue); + + private double WorldZ => default(Vector3).Transform(this.WorldMatrix()).Z; + + public IEnumerable ProcessCGcode(string lineToWrite, PrinterConfig printer) + { + if (!hasBeenReached + && lineToWrite.StartsWith("; LAYER_HEIGHT:")) + { + double layerHeight = 0; + if (GCodeFile.GetFirstNumberAfter("; LAYER_HEIGHT", lineToWrite, ref layerHeight, out _, stopCheckingString: ":")) + { + accumulatedLayerHeight += layerHeight; + if (accumulatedLayerHeight > WorldZ) + { + hasBeenReached = true; + yield return $"{GCodeToSend} ; G-Code from Scene Object"; + } + } + } + } + + public void Reset() + { + hasBeenReached = false; + accumulatedLayerHeight = 0; + } + + + public void DrawEditor(Object3DControlsLayer object3DControlLayer, DrawEventArgs e) + { + if (displayInfo.worldZ != WorldZ + || displayInfo.gcode != DisplayGCode) + { + UpdateTexture(); + } + } + + private string DisplayGCode + { + get + { + var max40Chars = GCodeToSend; + if (GCodeToSend.Length > 35) + { + max40Chars = GCodeToSend.Substring(0, 35) + "..."; + } + EnglishTextWrapping wrapper = new EnglishTextWrapping(10); + max40Chars = wrapper.InsertCRs(max40Chars, 120); + return max40Chars; + } + } + + private void UpdateTexture() + { + Mesh.FaceTextures.Clear(); + displayInfo.worldZ = WorldZ; + displayInfo.gcode = DisplayGCode; + var theme = AppContext.Theme; + var texture = new ImageBuffer(128, 128, 32); + var graphics2D = texture.NewGraphics2D(); + graphics2D.Clear(theme.BackgroundColor); + graphics2D.DrawString($"Height: {displayInfo.worldZ:0.##}", + texture.Width / 2, + texture.Height * .7, + 14, + Agg.Font.Justification.Center, + Agg.Font.Baseline.BoundsCenter, + theme.TextColor); + graphics2D.DrawString($"G-Code", + texture.Width / 2, + texture.Height * .45, + 14, + Agg.Font.Justification.Center, + Agg.Font.Baseline.BoundsCenter, + theme.TextColor); + var height = texture.Height * .37; + graphics2D.Line(texture.Width / 5, height, texture.Width / 5 * 4, height, theme.TextColor); + graphics2D.DrawString($"{displayInfo.gcode}", + texture.Width / 2, + texture.Height * .3, + 10, + Agg.Font.Justification.Center, + Agg.Font.Baseline.BoundsCenter, + theme.TextColor); + Mesh.PlaceTextureOnFaces(0, texture); + } + + public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer) + { + return AxisAlignedBoundingBox.Empty(); + } + + public bool DoEditorDraw(bool isSelected) + { + return true; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Primitives/SetTemperatureObject3D.cs b/MatterControlLib/DesignTools/Primitives/SetTemperatureObject3D.cs index 9db9a76ad..436bed2ca 100644 --- a/MatterControlLib/DesignTools/Primitives/SetTemperatureObject3D.cs +++ b/MatterControlLib/DesignTools/Primitives/SetTemperatureObject3D.cs @@ -43,7 +43,7 @@ using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.DesignTools { - public class SetTemperatureObject3D : Object3D, IObject3DControlsProvider, IGCodeTransformer, IEditorDraw + public class SetTemperatureObject3D : Object3D, IObject3DControlsProvider, IGCodeTransformer, IEditorDrawControled { private bool hasBeenReached; private double accumulatedLayerHeight; @@ -178,5 +178,10 @@ namespace MatterHackers.MatterControl.DesignTools { return AxisAlignedBoundingBox.Empty(); } + + public bool DoEditorDraw(bool isSelected) + { + return true; + } } } \ No newline at end of file diff --git a/MatterControlLib/Library/Providers/MatterControl/CalibrationPartsContainer.cs b/MatterControlLib/Library/Providers/MatterControl/CalibrationPartsContainer.cs index 865ab6ea9..77e5a3244 100644 --- a/MatterControlLib/Library/Providers/MatterControl/CalibrationPartsContainer.cs +++ b/MatterControlLib/Library/Providers/MatterControl/CalibrationPartsContainer.cs @@ -39,7 +39,7 @@ using MatterHackers.MatterControl.DesignTools; namespace MatterHackers.MatterControl.Library { - public class CalibrationPartsContainer : LibraryContainer + public class CalibrationPartsContainer : LibraryContainer { public CalibrationPartsContainer() { @@ -53,13 +53,6 @@ namespace MatterHackers.MatterControl.Library var oemParts = StaticData.Instance.GetFiles(Path.Combine("OEMSettings", "SampleParts")); Items = new SafeList(oemParts.Select(s => new StaticDataItem(s))); - Items.Add(new GeneratorItem( - "Set Temperature".Localize(), - async () => await SetTemperatureObject3D.Create()) - { - Category = this.Name - }); - Items.Add(new GeneratorItem( "PLA Temperature Tower".Localize(), async () => await TemperatureTowerObject3D.Create(220)) diff --git a/MatterControlLib/Library/Providers/MatterControl/DesignAppsCollectionContainer.cs b/MatterControlLib/Library/Providers/MatterControl/DesignAppsCollectionContainer.cs index 3bdd672b9..52b5d0c91 100644 --- a/MatterControlLib/Library/Providers/MatterControl/DesignAppsCollectionContainer.cs +++ b/MatterControlLib/Library/Providers/MatterControl/DesignAppsCollectionContainer.cs @@ -56,6 +56,16 @@ namespace MatterHackers.MatterControl.Library IsReadOnly = true }); + this.ChildContainers.Add( + new DynamicContainerLink( + "Scripting".Localize(), + StaticData.Instance.LoadIcon(Path.Combine("Library", "folder.png")), + StaticData.Instance.LoadIcon(Path.Combine("Library", "scripting_icon.png")), + () => new ScriptingPartsContainer()) + { + IsReadOnly = true + }); + this.ChildContainers.Add( new DynamicContainerLink( "Primitives".Localize(), diff --git a/MatterControlLib/Library/Providers/MatterControl/ScriptingPartsContainer.cs b/MatterControlLib/Library/Providers/MatterControl/ScriptingPartsContainer.cs new file mode 100644 index 000000000..08f1477da --- /dev/null +++ b/MatterControlLib/Library/Providers/MatterControl/ScriptingPartsContainer.cs @@ -0,0 +1,124 @@ +/* +Copyright (c) 2019, John Lewin +Copyright (c) 2021, Lars Brubaker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.Agg.Platform; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.DesignTools; + +namespace MatterHackers.MatterControl.Library +{ + public class ScriptingPartsContainer : LibraryContainer + { + public ScriptingPartsContainer() + { + this.ChildContainers = new SafeList(); + this.Items = new SafeList(); + this.Name = "Scripting".Localize(); + } + + public override void Load() + { + Items = new SafeList(); + + Items.Add(new GeneratorItem( + "Set Temperature".Localize(), + async () => await SetTemperatureObject3D.Create()) + { + Category = this.Name + }); + + Items.Add(new GeneratorItem( + "Send G-Code".Localize(), + async () => await SendGCodeObject3D.Create()) + { + Category = this.Name + }); + } + + private class StaticDataItem : ILibraryAssetStream + { + public StaticDataItem() + { + } + + public StaticDataItem(string relativePath) + { + this.AssetPath = relativePath; + } + + public string FileName => Path.GetFileName(AssetPath); + + public string ContentType => Path.GetExtension(AssetPath).ToLower().Trim('.'); + + public string AssetPath { get; } + + public long FileSize { get; } = -1; + + public bool LocalContentExists => true; + + public string Category { get; } = ""; + + public string ID => agg_basics.GetLongHashCode(AssetPath).ToString(); + + public event EventHandler NameChanged; + + public string Name + { + get => this.FileName; + set + { + // do nothing (can't rename) + } + } + + public bool IsProtected => true; + + public bool IsVisible => true; + + public DateTime DateModified { get; } = DateTime.Now; + + public DateTime DateCreated { get; } = DateTime.Now; + + public Task GetStream(Action progress) + { + return Task.FromResult(new StreamAndLength() + { + Stream = StaticData.Instance.OpenStream(AssetPath), + Length = -1 + }); + } + } + } +} diff --git a/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs b/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs index 5713b9559..1f0e7ee66 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs @@ -1088,7 +1088,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } } - foreach (var item in scene.Descendants().Where(i => i is ICustomEditorDraw customEditorDraw1 && customEditorDraw1.DoEditorDraw(i == selectedItem))) + foreach (var item in scene.Descendants().Where(i => i is IEditorDrawControled customEditorDraw1 && customEditorDraw1.DoEditorDraw(i == selectedItem))) { editorDrawItems.Add(item); } diff --git a/StaticData/Icons/Library/scripting_icon.png b/StaticData/Icons/Library/scripting_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e65879721a918978851ab48e7b43dfa29f75949f GIT binary patch literal 6677 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 Y1{t-em6Gba4uJ|fPgg&ebxsLQ03@if0ssI2 literal 0 HcmV?d00001 diff --git a/StaticData/Images/Thumbnails/11842437371347377518-256x256.png b/StaticData/Images/Thumbnails/11842437371347377518-256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..46a4bbf972edd9ae37c5a4273c35677cee4169bf GIT binary patch literal 21244 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

KWCk%JGt@5yt8Rj|2!9V zVK{V{ujaCe=h7gq0Ct<%|5t}d*4S8o$gz<5_F`9G=By45kP?X~U9mT1)gRB#S{B)K z@WH!n+qV4=ijDo7zb5SUGWRI8$s5HgwC8hH!U<_=Ca5sVX?8Yv9YmRwr+h`V)fKr zgpq^4Yt<^y1m>qFo(yemZ5_uSAN{moPL&8l-uBt+*6I1pvjLg5YSpQJ4JH9O5%}Qx zm8(|^zq|-kn#i#-WYrtH)pzrbzyE&u+BLW6Xz8t6w??cD`zh$ka7g?t6KDYa{`=<# z8kr^90;k26bsue7waQ|yUr6ZG2jYx1Q{>W&Bxg$fYMR+_HS6fZ0+H8mi(I7YqP634 zmi|*(mN^Ns=pxCGsj;DlM=0fxe8Uy>EIfx5M7RtsEj>-7?*0hZRM;EJvfy~q#;J!yF5J2W?Mq9=UVj=c*5c14 z1MAms+_sIaA#+w_0_%nCO$mNR|gva_>KSJ}*aBpw|bd(@z3)4D{~#LWi2!iNna zVq6RnH|?|+?es@XN~+*gOyWEm{=bMS20L0uUNfml~01; zj%od?bpx4}Gz7T1wmvJc=-0G`YOsgF_gmV69Jqu3f)w zJSBr|!4xKg#;4iW*TrUKWOzKYo_SO!AvJYr#B+|O39tLtu32-(^jCODNP>jh$utJ9 zxr|2|y;rR|Cz{5z=%R+Qva*k_??exkC*k5PHk~e@lGM%3t&6)M$1GZG2D4sZlVn2> zw*qTV?7o_xldiuuWi;`9d}^vT4?q9r9Xm848x)?n|37-vwXMB!m z17G3UXVcO)-%L%IamT7*(wceNU)RmiSrE1MTG{l|TZ>CeLk)SFQ;a-wb9JrD-yM1V z_0%(k#M_g+md=ivqprI<2K)6gNTTT1?$)M$N&2@J!XHM>|uiqH*?a| zCPy;eW2ke@Qxu2}WS9_rAfeN3arNt0uVy80oRK8K_OL)A_IlyYm@C(=b+NSMivRii z`SYId_p0}NJSIKIzJ6cC@7c>{ve~ix(=S*YviSdEAqTdO<(D(#LqkK`7ir9MQF7Fp znv|H>`1Nb4mbSJl)1hrV|K|UFlm6xFSJCo+k>L4Mfe*nCt}m7OD?gQiEAqgvSFf@{ zi;IhACrb3C7(%y!RDIj|3inI454HA%o=k+=u7<+rV~$m z#p2@PzP-7FJiBS(-mj^)2iF)fm3re*5jRL<#K!nh(?u2&hK=*gTU- z!y{p(LZfZ2?U zLyczkOgxz)*08lZJAO7M#rU)CL1zhcFKq>W3iU%x)RQSgEJ0qZAazjIH? z{VY1M_-%u{1&a)8pZ&)p!v0yao<+yTZWYN|y2$X;_H$M;d?y)NHl1SNS{`?|`kt$+ zE34UTUD^KQ(;EXH*gmk?8CxglwdG>^YK{dz=6q7BSf$W3Yu2pVRjaP07)ieV^=sFr zef$1hWS=hA5RjH*^LE#wMNFZgn@^?~&GlM3?d00$`YivR);v%*|7|=yStM7)(fk7E zmWZ|2+;-o6xB2$lu-$jp-N-Qmxj^!-@l2kRDVMI4${sc_T9)Z{^6k_4D%%(5FPOu$ ze-7)0K$biOhBqgtbNp_6RdZ8;X!-QyKl2X5_2(Qmih6-| O0D8LmxvX Date: Fri, 17 Jun 2022 12:23:56 -0700 Subject: [PATCH 2/5] better wire frame updating --- Submodules/agg-sharp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index d82343531..6e895b37b 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit d8234353154f15256cf785d9665bdc8f5636eee9 +Subproject commit 6e895b37b881cd6d00b0247ac349fa19538ca76a From e7f22f18956a8191e21ac144c4ddfc5e066cbeb3 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sat, 18 Jun 2022 20:40:06 -0700 Subject: [PATCH 3/5] improving bed rendering --- .../SceneViewer/FloorDrawable.cs | 18 +++++++++++++++--- .../View3D/Gui3D/MoveInZControl.cs | 6 ++++-- .../View3D/Object3DControlsLayer.cs | 18 ++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/MatterControlLib/PartPreviewWindow/SceneViewer/FloorDrawable.cs b/MatterControlLib/PartPreviewWindow/SceneViewer/FloorDrawable.cs index a6a758fb9..5373e4b77 100644 --- a/MatterControlLib/PartPreviewWindow/SceneViewer/FloorDrawable.cs +++ b/MatterControlLib/PartPreviewWindow/SceneViewer/FloorDrawable.cs @@ -89,8 +89,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public DrawStage DrawStage { get; } = DrawStage.First; public bool LookingDownOnBed { get; set; } + public bool SelectedObjectUnderBed { get; set; } - public void Draw(GuiWidget sender, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world) + public void Draw(GuiWidget sender, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world) { if (!sceneContext.RendererOptions.RenderBed) { @@ -101,13 +102,24 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { this.EnsureBedTexture(sceneContext.Scene.SelectedItem); + var alpha = 255; + if (SelectedObjectUnderBed) + { + alpha = 200; + } + if (!LookingDownOnBed) + { + alpha = 32; + } + + GL.Disable(EnableCap.Lighting); GLHelper.Render( sceneContext.Mesh, - theme.UnderBedColor.WithAlpha(32), + Color.White.WithAlpha(alpha), RenderTypes.Shaded, world.ModelviewMatrix, - blendTexture: !this.LookingDownOnBed, forceCullBackFaces: false); + GL.Enable(EnableCap.Lighting); if (sceneContext.PrinterShape != null) { diff --git a/MatterControlLib/PartPreviewWindow/View3D/Gui3D/MoveInZControl.cs b/MatterControlLib/PartPreviewWindow/View3D/Gui3D/MoveInZControl.cs index 848c94d5c..32cc49ddb 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Gui3D/MoveInZControl.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Gui3D/MoveInZControl.cs @@ -214,8 +214,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow zHeightDisplayInfo.Visible = true; - double distanceToHit = Vector3Ex.Dot(mouseEvent3D.info.HitPosition, mouseEvent3D.MouseRay.directionNormal); - hitPlane = new PlaneShape(mouseEvent3D.MouseRay.directionNormal, distanceToHit, null); + var upNormal = Vector3.UnitZ; + var sideNormal = upNormal.Cross(mouseEvent3D.MouseRay.directionNormal).GetNormal(); + var planeNormal = upNormal.Cross(sideNormal).GetNormal(); + hitPlane = new PlaneShape(new Plane(planeNormal, mouseEvent3D.info.HitPosition), null); initialHitPosition = mouseEvent3D.info.HitPosition; transformOnMouseDown = selectedItem.Matrix; diff --git a/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs b/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs index 1f0e7ee66..318c65fc7 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/Object3DControlsLayer.cs @@ -1126,11 +1126,21 @@ namespace MatterHackers.MatterControl.PartPreviewWindow var bedNormalInViewSpace = Vector3Ex.TransformNormal(Vector3.UnitZ, World.ModelviewMatrix).GetNormal(); var pointOnBedInViewSpace = Vector3Ex.Transform(new Vector3(10, 10, 0), World.ModelviewMatrix); - var lookingDownOnBed = Vector3Ex.Dot(bedNormalInViewSpace, pointOnBedInViewSpace) < 0; + floorDrawable.LookingDownOnBed = Vector3Ex.Dot(bedNormalInViewSpace, pointOnBedInViewSpace) < 0; - floorDrawable.LookingDownOnBed = lookingDownOnBed; + floorDrawable.SelectedObjectUnderBed = false; + if (selectedItem != null) + { + var aabb = selectedItem.GetAxisAlignedBoundingBox(); + if (aabb.MinXYZ.Z < 0) + { + floorDrawable.SelectedObjectUnderBed = true; + } + } - if (lookingDownOnBed) + var renderBedTransparent = !floorDrawable.LookingDownOnBed || floorDrawable.SelectedObjectUnderBed; + + if (renderBedTransparent) { floorDrawable.Draw(this, e, Matrix4X4.Identity, this.World); } @@ -1174,7 +1184,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow forceCullBackFaces: false); } - if (!lookingDownOnBed) + if (!renderBedTransparent) { floorDrawable.Draw(this, e, Matrix4X4.Identity, this.World); } From e91decdc121f53c8ad234b0dae9752a3d2542125 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 19 Jun 2022 15:02:05 -0700 Subject: [PATCH 4/5] Adding overflow text Adding tool bar quick buttons --- .../CustomWidgets/DockingTabControl.cs | 2 +- .../PartPreviewWindow/MarkdownEditPage.cs | 2 +- .../PartPreviewWindow/PopupMenu.cs | 49 +++++- .../PartPreviewWindow/PrinterTabPage.cs | 2 +- .../PartPreviewWindow/SelectedObjectPanel.cs | 7 +- MatterControlLib/PartPreviewWindow/Tabs.cs | 6 +- .../View3D/PrinterBar/OverflowBar.cs | 122 ++++++++------ .../View3D/PrinterBar/PrinterActionsBar.cs | 3 +- .../PartPreviewWindow/ViewToolBarControls.cs | 157 +++++++++++------- .../SliceSettingsWidget.cs | 2 +- .../UIFields/EnumDisplayField.cs | 16 ++ 11 files changed, 243 insertions(+), 125 deletions(-) diff --git a/MatterControlLib/CustomWidgets/DockingTabControl.cs b/MatterControlLib/CustomWidgets/DockingTabControl.cs index 6457aaa0c..5b11b3f31 100644 --- a/MatterControlLib/CustomWidgets/DockingTabControl.cs +++ b/MatterControlLib/CustomWidgets/DockingTabControl.cs @@ -227,7 +227,7 @@ namespace MatterHackers.MatterControl.CustomWidgets this.ConstrainedWidth = resizePage.Width; }; - tabControl = new SimpleTabs(theme, this.CreatePinButton()) + tabControl = new SimpleTabs(theme, null, this.CreatePinButton()) { VAnchor = VAnchor.Stretch, HAnchor = HAnchor.Stretch, diff --git a/MatterControlLib/PartPreviewWindow/MarkdownEditPage.cs b/MatterControlLib/PartPreviewWindow/MarkdownEditPage.cs index 5a6850459..71e7ddadf 100644 --- a/MatterControlLib/PartPreviewWindow/MarkdownEditPage.cs +++ b/MatterControlLib/PartPreviewWindow/MarkdownEditPage.cs @@ -47,7 +47,7 @@ namespace MatterHackers.MatterControl this.WindowTitle = "MatterControl - " + "Markdown Edit".Localize(); this.HeaderText = "Edit Page".Localize() + ":"; - var tabControl = new SimpleTabs(theme, new GuiWidget()) + var tabControl = new SimpleTabs(theme, null, new GuiWidget()) { HAnchor = HAnchor.Stretch, VAnchor = VAnchor.Stretch, diff --git a/MatterControlLib/PartPreviewWindow/PopupMenu.cs b/MatterControlLib/PartPreviewWindow/PopupMenu.cs index 1551e132d..9221cca85 100644 --- a/MatterControlLib/PartPreviewWindow/PopupMenu.cs +++ b/MatterControlLib/PartPreviewWindow/PopupMenu.cs @@ -59,12 +59,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.BackgroundColor = theme.BackgroundColor; } - public HorizontalLine CreateSeparator() + public HorizontalLine CreateSeparator(double height = 1) { var line = new HorizontalLine(ApplicationController.Instance.MenuTheme.BorderColor20) { Margin = new BorderDouble(8, 1), - BackgroundColor = theme.RowBorder + BackgroundColor = theme.RowBorder, + Height = height * DeviceScale, }; this.AddChild(line); @@ -430,6 +431,20 @@ namespace MatterHackers.MatterControl.PartPreviewWindow return this.CreateButtonSelectMenuItem(textWidget, text, buttonKvps, startingValue, setter, minSpacerWidth); } + public MenuItem CreateButtonMenuItem(string text, + IEnumerable<(string key, string text, EventHandler click)> buttonKvps, + double minSpacerWidth = 0, + bool bold = false) + { + var textWidget = new TextWidget(text, pointSize: theme.DefaultFontSize, textColor: theme.TextColor, bold: bold) + { + Padding = MenuPadding, + VAnchor = VAnchor.Center, + }; + + return this.CreateButtonMenuItem(textWidget, text, buttonKvps, minSpacerWidth); + } + public MenuItem CreateButtonSelectMenuItem(string text, ImageBuffer icon, IEnumerable<(string key, string text)> buttonKvps, string startingValue, Action setter) { var row = new FlowLayoutWidget() @@ -481,6 +496,36 @@ namespace MatterHackers.MatterControl.PartPreviewWindow return menuItem; } + public MenuItem CreateButtonMenuItem(GuiWidget guiWidget, string name, IEnumerable<(string key, string text, EventHandler click)> buttonKvps, double minSpacerWidth = 0) + { + var row = new FlowLayoutWidget() + { + HAnchor = HAnchor.MaxFitOrStretch, + Name = name + " Menu Item", + }; + + row.AddChild(guiWidget); + row.AddChild(new HorizontalSpacer() + { + MinimumSize = new Vector2(minSpacerWidth, 0) + }); + + foreach (var buttonKvp in buttonKvps) + { + var button = EnumDisplayField.CreateThemedButton(buttonKvp.text, buttonKvp.key, "", theme); + button.Click += buttonKvp.click; + row.AddChild(button); + } + + var menuItem = new MenuItemHoldOpen(row, theme) + { + }; + + this.AddChild(menuItem); + + return menuItem; + } + public MenuItem CreateMenuItem(GuiWidget guiWidget, string name, ImageBuffer icon = null) { var menuItem = new MenuItem(guiWidget, theme) diff --git a/MatterControlLib/PartPreviewWindow/PrinterTabPage.cs b/MatterControlLib/PartPreviewWindow/PrinterTabPage.cs index 8be749bb0..edb542f28 100644 --- a/MatterControlLib/PartPreviewWindow/PrinterTabPage.cs +++ b/MatterControlLib/PartPreviewWindow/PrinterTabPage.cs @@ -608,7 +608,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { sideBar.AddPage( "Printer", - "Printer".Localize(), + "Printer Settings".Localize(), new ConfigurePrinterWidget(sliceSettingsWidget.SettingsContext, Printer, theme) { HAnchor = HAnchor.Stretch, diff --git a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs index ca0025b04..9f4217a8b 100644 --- a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs +++ b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs @@ -131,11 +131,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow }; toolbar.AddChild(cancelButton); - overflowButton = new OverflowBar.OverflowMenuButton(theme) + overflowButton = new PopupMenuButton("Action".Localize(), theme) { Enabled = scene.SelectedItem != null, + DrawArrow = true, }; - overflowButton.ToolTipText = "Selected Object Options".Localize(); + overflowButton.ToolTipText = "Object Actions".Localize(); overflowButton.DynamicPopupContent = () => { return ApplicationController.Instance.GetModifyMenu(view3DWidget.sceneContext); @@ -175,7 +176,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private readonly JsonPathContext pathGetter = new JsonPathContext(); private readonly IconButton applyButton; private readonly IconButton cancelButton; - private readonly OverflowBar.OverflowMenuButton overflowButton; + private readonly PopupMenuButton overflowButton; private readonly InteractiveScene scene; private readonly FlowLayoutWidget primaryActionsPanel; diff --git a/MatterControlLib/PartPreviewWindow/Tabs.cs b/MatterControlLib/PartPreviewWindow/Tabs.cs index 0b99f5cef..c6f445e39 100644 --- a/MatterControlLib/PartPreviewWindow/Tabs.cs +++ b/MatterControlLib/PartPreviewWindow/Tabs.cs @@ -58,14 +58,14 @@ namespace MatterHackers.MatterControl.PartPreviewWindow /// public class SimpleTabs : FlowLayoutWidget { - public SimpleTabs(ThemeConfig theme, GuiWidget rightAnchorItem = null) + public SimpleTabs(ThemeConfig theme, string overflowText, GuiWidget rightAnchorItem = null) : base(FlowDirection.TopToBottom) { this.TabContainer = this; if (rightAnchorItem == null) { - TabBar = new OverflowBar(theme) + TabBar = new OverflowBar(null, theme, overflowText) { HAnchor = HAnchor.Stretch, VAnchor = VAnchor.Fit @@ -252,7 +252,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public event EventHandler PlusClicked; public ChromeTabs(GuiWidget rightAnchorItem, ThemeConfig theme) - : base(theme, rightAnchorItem) + : base(theme, null, rightAnchorItem) { leadingTabAdornment = new GuiWidget() { diff --git a/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/OverflowBar.cs b/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/OverflowBar.cs index 63d239673..6310b57d1 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/OverflowBar.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/OverflowBar.cs @@ -51,17 +51,28 @@ namespace MatterHackers.MatterControl.PartPreviewWindow : this(null, theme) { } - public OverflowBar(ImageBuffer icon, ThemeConfig theme) + public OverflowBar(ImageBuffer icon, ThemeConfig theme, string text = null) : base(theme.TabbarPadding) { this.theme = theme; if (icon == null) { - this.OverflowButton = new OverflowMenuButton(this, theme) + if (text != null) { - AlignToRightEdge = true, - }; + this.OverflowButton = new PopupMenuButton(text, theme) + { + AlignToRightEdge = true, + DrawArrow = true, + }; + } + else + { + this.OverflowButton = new OverflowMenuButton(this, theme) + { + AlignToRightEdge = true, + }; + } } else { @@ -70,6 +81,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow AlignToRightEdge = true, }; } + AddMenuItemsFunction(OverflowButton); // We want to set right margin to overflow button width but width is already scaled - need to inflate value by amount needed to hit width when rescaled in Margin setter this.ActionArea.Margin = new BorderDouble(right: Math.Ceiling(this.OverflowButton.Width / GuiWidget.DeviceScale)); @@ -78,7 +90,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private ThemeConfig theme; - public OverflowMenuButton OverflowButton { get; } + public PopupMenuButton OverflowButton { get; } public Action ExtendOverflowMenu { get; set; } @@ -120,6 +132,55 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } } + private void AddMenuItemsFunction(PopupMenuButton menuButton) + { + menuButton.DynamicPopupContent = () => + { + var menuTheme = ApplicationController.Instance.MenuTheme; + var popupMenu = new PopupMenu(menuTheme); + + // Perform overflow + bool hasOverflowItems = false; + foreach (var widget in this.ActionArea.Children.Where(c => !c.Visible && !ignoredInMenuTypes.Contains(c.GetType()))) + { + if (widget is ToolbarSeparator) + { + popupMenu.CreateSeparator(); + continue; + } + + hasOverflowItems = true; + + PopupMenu.MenuItem menuItem; + + var iconButton = widget as IconButton; + + var iconImage = iconButton?.IconImage; + + menuItem = popupMenu.CreateMenuItem( + widget.ToolTipText ?? widget.Text, + iconImage); + + menuItem.Enabled = widget.Enabled; + + menuItem.Click += (s, e) => + { + widget.InvokeClick(); + }; + } + + if (hasOverflowItems) + { + popupMenu.CreateSeparator(); + } + + // Extend menu with non-overflow/standard items + this.OnExtendPopupMenu(popupMenu); + + return popupMenu; + }; + } + ///

/// A PopupMenuButton with the standard overflow icon /// @@ -142,55 +203,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public OverflowMenuButton(OverflowBar overflowBar, ImageBuffer icon, ThemeConfig theme) : base(icon, theme) - { - this.DynamicPopupContent = () => - { - var menuTheme = ApplicationController.Instance.MenuTheme; - var popupMenu = new PopupMenu(menuTheme); + { + } - // Perform overflow - bool hasOverflowItems = false; - foreach (var widget in overflowBar.ActionArea.Children.Where(c => !c.Visible && !ignoredInMenuTypes.Contains(c.GetType()))) - { - if (widget is ToolbarSeparator) - { - popupMenu.CreateSeparator(); - continue; - } - - hasOverflowItems = true; - - PopupMenu.MenuItem menuItem; - - var iconButton = widget as IconButton; - - var iconImage = iconButton?.IconImage; - - menuItem = popupMenu.CreateMenuItem( - widget.ToolTipText ?? widget.Text, - iconImage); - - menuItem.Enabled = widget.Enabled; - - menuItem.Click += (s, e) => - { - widget.InvokeClick(); - }; - } - - if (hasOverflowItems) - { - popupMenu.CreateSeparator(); - } - - // Extend menu with non-overflow/standard items - overflowBar.OnExtendPopupMenu(popupMenu); - - return popupMenu; - }; - } - - private static ImageBuffer CreateOverflowIcon(ThemeConfig theme) + private static ImageBuffer CreateOverflowIcon(ThemeConfig theme) { return StaticData.Instance.LoadIcon(Path.Combine("ViewTransformControls", "overflow.png"), 32, 32).SetToColor(theme.TextColor); } diff --git a/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs b/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs index 98d4473e6..d726aaebc 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs @@ -64,7 +64,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private Dictionary viewModes = new Dictionary(); public PrinterActionsBar(PrinterConfig printer, PrinterTabPage printerTabPage, ThemeConfig theme) - : base(theme) + : base(null, theme, "Printer Options".Localize()) { this.printer = printer; this.printerTabPage = printerTabPage; @@ -232,7 +232,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } this.OverflowButton.Name = "Printer Overflow Menu"; - this.OverflowButton.ToolTipText = "Printer Options".Localize(); this.ExtendOverflowMenu = (popupMenu) => { this.GeneratePrinterOverflowMenu(popupMenu, ApplicationController.Instance.MenuTheme); diff --git a/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs b/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs index 380752770..481b3f109 100644 --- a/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs +++ b/MatterControlLib/PartPreviewWindow/ViewToolBarControls.cs @@ -298,10 +298,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow // add the options menu this.AddChild(new HorizontalSpacer()); - var overflowIcon = StaticData.Instance.LoadIcon(Path.Combine("ViewTransformControls", "overflow.png"), 32, 32).SetToColor(theme.TextColor); - var optionsButton = new PopupMenuButton(overflowIcon, theme) + optionsButton = new PopupMenuButton("Tools".Localize(), theme) { - AlignToRightEdge = true + AlignToRightEdge = true, + DrawArrow = true, }; this.AddChild(optionsButton); optionsButton.Name = "ToolBar Overflow Menu"; @@ -336,6 +336,22 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private string Default; private string Hide_All; private string Expand_All; + private PopupMenuButton optionsButton; + + private IEnumerable GroupOperations + + { + get + { + foreach (var namedAction in SceneOperations.All) + { + if (namedAction is OperationGroup operationGroup) + { + yield return operationGroup; + } + } + } + } private PopupMenu GenerateToolBarOptionsMenu(ThemeConfig theme) { @@ -344,36 +360,64 @@ namespace MatterHackers.MatterControl.PartPreviewWindow Padding = new BorderDouble(0, 7), }; -#if false - // buttons for the control of defaults - var topButtonData = new (string, string)[] +#if true + void DefaultClicked(object e, MouseEventArgs mouseEvent) { - (nameof(Default), "Default".Localize()), - (nameof(Expand_All), "Expand All".Localize()), - (nameof(Hide_All), "Hide All".Localize()), + foreach (var operationGroup in GroupOperations) + { + switch(operationGroup.Id) + { + case "Path": + case "Printing": + case "Design Apps": + operationGroup.Visible = false; + break; + + default: + operationGroup.Collapse = true; + operationGroup.Visible = true; + break; + } + } + + optionsButton.InvokeClick(); + UiThread.RunOnIdle(optionsButton.InvokeClick); + } + + void HideAll(object e, MouseEventArgs mouseEvent) + { + foreach (var operationGroup in GroupOperations) + { + operationGroup.Visible = false; + } + + optionsButton.InvokeClick(); + UiThread.RunOnIdle(optionsButton.InvokeClick); + } + + void ExpandAll(object e, MouseEventArgs mouseEvent) + { + foreach (var operationGroup in GroupOperations) + { + operationGroup.Collapse = false; + operationGroup.Visible = true; + } + + optionsButton.InvokeClick(); + UiThread.RunOnIdle(optionsButton.InvokeClick); + } + + // buttons for the control of defaults + var topButtonData = new (string, string, EventHandler)[] + { + (nameof(Default), nameof(Default).Replace("_", " ").Localize(), DefaultClicked), + (nameof(Expand_All), nameof(Expand_All).Replace("_", " ").Localize(), ExpandAll), + (nameof(Hide_All), nameof(Hide_All).Replace("_", " ").Localize(), HideAll), }; - popupMenu.CreateButtonSelectMenuItem("Group Setings".Localize(), topButtonData, nameof(Default), (value) => - { - switch (value) - { - case nameof(Expanded): - operationGroup.Collapse = false; - operationGroup.Visible = true; - break; + popupMenu.CreateButtonMenuItem("Design Tools".Localize(), topButtonData, 40 * GuiWidget.DeviceScale, true); - case nameof(Collapsed): - operationGroup.Collapse = true; - operationGroup.Visible = true; - break; - - case nameof(Hidden): - operationGroup.Visible = false; - break; - } - }, 40 * GuiWidget.DeviceScale); - - popupMenu.CreateSeparator(); + popupMenu.CreateSeparator(5); #endif // the buttons per setting @@ -384,38 +428,35 @@ namespace MatterHackers.MatterControl.PartPreviewWindow (nameof(Hidden), "Hide".Localize()), }; - foreach (var namedAction in SceneOperations.All) + foreach (var operationGroup in GroupOperations) { - if (namedAction is OperationGroup operationGroup) + var startingValue = operationGroup.Collapse ? nameof(Collapsed) : nameof(Expanded); + if (!operationGroup.Visible) { - var startingValue = operationGroup.Collapse ? nameof(Collapsed) : nameof(Expanded); - if(!operationGroup.Visible) - { - startingValue = nameof(Hidden); - } - - popupMenu.CreateButtonSelectMenuItem(operationGroup.Title, buttonData, startingValue, (value) => - { - switch (value) - { - case nameof(Expanded): - operationGroup.Collapse = false; - operationGroup.Visible = true; - break; - - case nameof(Collapsed): - operationGroup.Collapse = true; - operationGroup.Visible = true; - break; - - case nameof(Hidden): - operationGroup.Visible = false; - break; - } - }, 40 * GuiWidget.DeviceScale); - - popupMenu.CreateSeparator(); + startingValue = nameof(Hidden); } + + popupMenu.CreateButtonSelectMenuItem(operationGroup.Title, buttonData, startingValue, (value) => + { + switch (value) + { + case nameof(Expanded): + operationGroup.Collapse = false; + operationGroup.Visible = true; + break; + + case nameof(Collapsed): + operationGroup.Collapse = true; + operationGroup.Visible = true; + break; + + case nameof(Hidden): + operationGroup.Visible = false; + break; + } + }, 40 * GuiWidget.DeviceScale); + + popupMenu.CreateSeparator(); }; return popupMenu; diff --git a/MatterControlLib/SlicerConfiguration/SliceSettingsWidget.cs b/MatterControlLib/SlicerConfiguration/SliceSettingsWidget.cs index 0326c3fe1..12a735246 100644 --- a/MatterControlLib/SlicerConfiguration/SliceSettingsWidget.cs +++ b/MatterControlLib/SlicerConfiguration/SliceSettingsWidget.cs @@ -182,7 +182,7 @@ namespace MatterHackers.MatterControl.SlicerConfiguration string databaseMRUKey, string justMySettingsTitle, Action extendPopupMenu = null) - : base(theme) + : base(theme, "View".Localize()) { using (this.LayoutLock()) { diff --git a/MatterControlLib/SlicerConfiguration/UIFields/EnumDisplayField.cs b/MatterControlLib/SlicerConfiguration/UIFields/EnumDisplayField.cs index 7c09ee1b7..401078f4f 100644 --- a/MatterControlLib/SlicerConfiguration/UIFields/EnumDisplayField.cs +++ b/MatterControlLib/SlicerConfiguration/UIFields/EnumDisplayField.cs @@ -294,6 +294,22 @@ namespace MatterHackers.MatterControl.SlicerConfiguration return radioButton; } + public static TextButton CreateThemedButton(string text, string key, string toolTipText, ThemeConfig theme) + { + var button = new TextButton(text, theme) + { + VAnchor = VAnchor.Center | VAnchor.Fit, + BackgroundRadius = (theme.ButtonRadius + 4) * GuiWidget.DeviceScale, + Margin = new BorderDouble(5, 0, 0, 0), + Padding = new BorderDouble(9, 5), + // BackgroundInset = new BorderDouble(5, 4), + BackgroundColor = theme.MinimalShade, + ToolTipText = toolTipText + }; + + return button; + } + private void AddIconRow(List<(string key, string name, string description)> items) { var iconsRow = new FlowLayoutWidget(); From fb86f7c96b44485bbcecec6ac679bc776f348c3a Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Sun, 19 Jun 2022 20:13:32 -0700 Subject: [PATCH 5/5] Fixing tests --- .../SetupWizard/PrinterProfileHistoryPage.cs | 7 ++++++- .../SlicerConfiguration/Settings/ProfileManager.cs | 7 ++++++- Submodules/agg-sharp | 2 +- Tests/MatterControl.AutomationTests/PrintingTests.cs | 2 +- .../SliceSettingsTests.cs | 1 + .../MatterControl/MatterControlUtilities.cs | 10 ++++------ 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/MatterControlLib/SetupWizard/PrinterProfileHistoryPage.cs b/MatterControlLib/SetupWizard/PrinterProfileHistoryPage.cs index 6bc1bbee1..261b7bafe 100644 --- a/MatterControlLib/SetupWizard/PrinterProfileHistoryPage.cs +++ b/MatterControlLib/SetupWizard/PrinterProfileHistoryPage.cs @@ -78,7 +78,12 @@ namespace MatterHackers.MatterControl.SetupWizard var profile = ProfileManager.Instance[printer.Settings.ID]; - var results = await ApplicationController.GetProfileHistory?.Invoke(profile.DeviceToken); + Dictionary results = null; + if (ApplicationController.GetProfileHistory != null) + { + results = await ApplicationController.GetProfileHistory.Invoke(profile.DeviceToken); + } + printerProfileData = results; if(printerProfileData != null) { diff --git a/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs b/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs index 09020cde7..6f186bdc8 100644 --- a/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs +++ b/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs @@ -580,7 +580,12 @@ namespace MatterHackers.MatterControl.SlicerConfiguration private static async Task GetFirstValidHistoryItem(PrinterInfo printerInfo) { - var recentProfileHistoryItems = await ApplicationController.GetProfileHistory?.Invoke(printerInfo.DeviceToken); + Dictionary recentProfileHistoryItems = null; + if (ApplicationController.GetProfileHistory != null) + { + recentProfileHistoryItems = await ApplicationController.GetProfileHistory.Invoke(printerInfo.DeviceToken); + } + if (recentProfileHistoryItems != null) { // Iterate history, skipping the first item, limiting to the next five, attempt to load and return the first success diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 6e895b37b..338231d1f 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 6e895b37b881cd6d00b0247ac349fa19538ca76a +Subproject commit 338231d1f1bafe87d200435b065035846e75ca60 diff --git a/Tests/MatterControl.AutomationTests/PrintingTests.cs b/Tests/MatterControl.AutomationTests/PrintingTests.cs index b4b808488..aca1fe3c2 100644 --- a/Tests/MatterControl.AutomationTests/PrintingTests.cs +++ b/Tests/MatterControl.AutomationTests/PrintingTests.cs @@ -406,7 +406,7 @@ namespace MatterHackers.MatterControl.Tests.Automation // print a part testRunner.AddItemToBed() - .AddItemToBed(partName: "Row Item Set Temperature") + .AddItemToBed("Scripting Row Item Collection", "Row Item Set Temperature") .DragDropByName("MoveInZControl", "MoveInZControl", offsetDrag: new Point2D(0, 0), offsetDrop: new Point2D(0, 10)) .ClickByName("Temperature Edit") .Type("222.2") diff --git a/Tests/MatterControl.AutomationTests/SliceSettingsTests.cs b/Tests/MatterControl.AutomationTests/SliceSettingsTests.cs index 7c12e4fe9..6e8bdc416 100644 --- a/Tests/MatterControl.AutomationTests/SliceSettingsTests.cs +++ b/Tests/MatterControl.AutomationTests/SliceSettingsTests.cs @@ -586,6 +586,7 @@ namespace MatterHackers.MatterControl.Tests.Automation .SwitchToSliceSettings() .ClickByName("Slice Settings Overflow Menu") .ClickByName("Advanced Menu Item") + .Delay() .SelectSliceSettingsField(SettingsKey.bed_temperature) .SelectSliceSettingsField(SettingsKey.temperature) // Uncheck Has Heated Bed checkbox and make sure Bed Temp Textbox is not visible diff --git a/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs b/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs index bb49c856b..1f34380b5 100644 --- a/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs +++ b/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs @@ -717,6 +717,7 @@ namespace MatterHackers.MatterControl.Tests.Automation break; case "Calibration Parts Row Item Collection": + case "Scripting Row Item Collection": case "Primitives Row Item Collection": // If visible, navigate into Libraries container before opening target testRunner.DoubleClickByName("Design Apps Row Item Collection") @@ -1289,13 +1290,10 @@ namespace MatterHackers.MatterControl.Tests.Automation if (!testRunner.NameExists("Printer Tab", 0.1)) { testRunner.ClickByName("Printer Overflow Menu") - .ClickByName("Show Printer Menu Item"); - - if (!pinSettingsOpen) - { + .Delay() + .ClickByName("Show Printer Menu Item") // close the menu - testRunner.ClickByName("Printer Overflow Menu"); - } + .ClickByName("Printer Overflow Menu"); } if (pinSettingsOpen)