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/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/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/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/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/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 5713b9559..318c65fc7 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); } @@ -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); } 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/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/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(); diff --git a/StaticData/Icons/Library/scripting_icon.png b/StaticData/Icons/Library/scripting_icon.png new file mode 100644 index 000000000..e65879721 Binary files /dev/null and b/StaticData/Icons/Library/scripting_icon.png differ diff --git a/StaticData/Images/Thumbnails/11842437371347377518-256x256.png b/StaticData/Images/Thumbnails/11842437371347377518-256x256.png new file mode 100644 index 000000000..46a4bbf97 Binary files /dev/null and b/StaticData/Images/Thumbnails/11842437371347377518-256x256.png differ diff --git a/StaticData/Translations/Master.txt b/StaticData/Translations/Master.txt index a366998b7..580461830 100644 --- a/StaticData/Translations/Master.txt +++ b/StaticData/Translations/Master.txt @@ -1984,6 +1984,9 @@ Translated:Fuzzy Frequency English:Fuzzy Thickness Translated:Fuzzy Thickness +English:G Code To Send +Translated:G Code To Send + English:G Key Translated:G Key @@ -4369,6 +4372,9 @@ Translated:Scale Offset English:Scale Type Translated:Scale Type +English:Scripting +Translated:Scripting + English:SD Card Translated:SD Card @@ -4459,6 +4465,9 @@ Translated:Send English:Send email notifications Translated:Send email notifications +English:Send G-Code +Translated:Send G-Code + English:Send SMS notifications Translated:Send SMS notifications diff --git a/Submodules/MatterSlice b/Submodules/MatterSlice index b2b6261e0..cce755cd9 160000 --- a/Submodules/MatterSlice +++ b/Submodules/MatterSlice @@ -1 +1 @@ -Subproject commit b2b6261e0936f91a9f018da860c636dfafef2d91 +Subproject commit cce755cd9c356a96edb641ed515113caa6bf6ad3 diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 238b25271..338231d1f 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 238b25271497943ece9ab855b119810abe2c9809 +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)