From 89e227a428e92b78cc1916dfadd31fda30db3209 Mon Sep 17 00:00:00 2001 From: Nicholas Piegdon Date: Wed, 29 Jan 2020 11:15:16 -0500 Subject: [PATCH] Add Marlin 2.x and custom detection With any luck, this is all we'll need for #4. --- BedLeveler.Designer.cs | 90 ++++++++++++++++++++++++++++-- BedLeveler.cs | 111 +++++++++++++++++++++++++++++++------ BedLeveler.csproj | 1 + BedLeveler.resx | 27 +++++++++ Properties/AssemblyInfo.cs | 6 +- WindowsFormsExtensions.cs | 64 +++++++++++++++++++++ 6 files changed, 275 insertions(+), 24 deletions(-) create mode 100644 WindowsFormsExtensions.cs diff --git a/BedLeveler.Designer.cs b/BedLeveler.Designer.cs index 750be9c..547cb11 100644 --- a/BedLeveler.Designer.cs +++ b/BedLeveler.Designer.cs @@ -45,6 +45,11 @@ private void InitializeComponent() this.widthText = new System.Windows.Forms.TextBox(); this.maxZText = new System.Windows.Forms.TextBox(); this.minZText = new System.Windows.Forms.TextBox(); + this.tabDetection = new System.Windows.Forms.TabPage(); + this.checkCustom = new System.Windows.Forms.CheckBox(); + this.checkMarlin2 = new System.Windows.Forms.CheckBox(); + this.checkMarlin1 = new System.Windows.Forms.CheckBox(); + this.textCustom = new System.Windows.Forms.RichTextBox(); this.portText = new System.Windows.Forms.TextBox(); this.connectButton = new System.Windows.Forms.Button(); this.outputText = new System.Windows.Forms.TextBox(); @@ -71,6 +76,7 @@ private void InitializeComponent() tabBedSize.SuspendLayout(); tabColors.SuspendLayout(); tabs.SuspendLayout(); + this.tabDetection.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.bedPicture)).BeginInit(); this.SuspendLayout(); // @@ -95,7 +101,7 @@ private void InitializeComponent() tabBedSize.Location = new System.Drawing.Point(4, 22); tabBedSize.Name = "tabBedSize"; tabBedSize.Padding = new System.Windows.Forms.Padding(3); - tabBedSize.Size = new System.Drawing.Size(254, 93); + tabBedSize.Size = new System.Drawing.Size(254, 127); tabBedSize.TabIndex = 0; tabBedSize.Text = "Bed Size"; tabBedSize.UseVisualStyleBackColor = true; @@ -166,7 +172,7 @@ private void InitializeComponent() tabColors.Location = new System.Drawing.Point(4, 22); tabColors.Name = "tabColors"; tabColors.Padding = new System.Windows.Forms.Padding(3); - tabColors.Size = new System.Drawing.Size(254, 93); + tabColors.Size = new System.Drawing.Size(254, 127); tabColors.TabIndex = 1; tabColors.Text = "Colors"; tabColors.UseVisualStyleBackColor = true; @@ -237,12 +243,79 @@ private void InitializeComponent() tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); tabs.Controls.Add(tabBedSize); tabs.Controls.Add(tabColors); - tabs.Location = new System.Drawing.Point(12, 508); + tabs.Controls.Add(this.tabDetection); + tabs.Location = new System.Drawing.Point(12, 488); tabs.Name = "tabs"; tabs.SelectedIndex = 0; - tabs.Size = new System.Drawing.Size(262, 119); + tabs.Size = new System.Drawing.Size(262, 153); tabs.TabIndex = 4; // + // tabDetection + // + this.tabDetection.Controls.Add(this.checkCustom); + this.tabDetection.Controls.Add(this.checkMarlin2); + this.tabDetection.Controls.Add(this.checkMarlin1); + this.tabDetection.Controls.Add(this.textCustom); + this.tabDetection.Location = new System.Drawing.Point(4, 22); + this.tabDetection.Name = "tabDetection"; + this.tabDetection.Size = new System.Drawing.Size(254, 127); + this.tabDetection.TabIndex = 2; + this.tabDetection.Text = "Detection"; + this.tabDetection.UseVisualStyleBackColor = true; + // + // checkCustom + // + this.checkCustom.AutoSize = true; + this.checkCustom.Location = new System.Drawing.Point(6, 63); + this.checkCustom.Name = "checkCustom"; + this.checkCustom.Size = new System.Drawing.Size(61, 17); + this.checkCustom.TabIndex = 2; + this.checkCustom.Text = "Custom"; + this.checkCustom.UseVisualStyleBackColor = true; + this.checkCustom.CheckedChanged += new System.EventHandler(this.checkCustomChanged); + // + // checkMarlin2 + // + this.checkMarlin2.AutoSize = true; + this.checkMarlin2.Checked = true; + this.checkMarlin2.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkMarlin2.Location = new System.Drawing.Point(6, 40); + this.checkMarlin2.Name = "checkMarlin2"; + this.checkMarlin2.Size = new System.Drawing.Size(71, 17); + this.checkMarlin2.TabIndex = 1; + this.checkMarlin2.Text = "Marlin 2.x"; + this.checkMarlin2.UseVisualStyleBackColor = true; + this.checkMarlin2.CheckedChanged += new System.EventHandler(this.RedrawBedImage); + // + // checkMarlin1 + // + this.checkMarlin1.AutoSize = true; + this.checkMarlin1.Checked = true; + this.checkMarlin1.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkMarlin1.Location = new System.Drawing.Point(6, 17); + this.checkMarlin1.Name = "checkMarlin1"; + this.checkMarlin1.Size = new System.Drawing.Size(71, 17); + this.checkMarlin1.TabIndex = 0; + this.checkMarlin1.Text = "Marlin 1.x"; + this.checkMarlin1.UseVisualStyleBackColor = true; + this.checkMarlin1.CheckedChanged += new System.EventHandler(this.RedrawBedImage); + // + // textCustom + // + this.textCustom.DetectUrls = false; + this.textCustom.Enabled = false; + this.textCustom.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.textCustom.Location = new System.Drawing.Point(23, 86); + this.textCustom.Multiline = false; + this.textCustom.Name = "textCustom"; + this.textCustom.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None; + this.textCustom.ShortcutsEnabled = false; + this.textCustom.Size = new System.Drawing.Size(220, 22); + this.textCustom.TabIndex = 3; + this.textCustom.Text = "Bed X: {X} Y: {Y} Z: {Z}"; + this.textCustom.WordWrap = false; + this.textCustom.TextChanged += new System.EventHandler(this.customDetectionChanged); + // // portText // this.portText.Location = new System.Drawing.Point(12, 14); @@ -268,7 +341,7 @@ private void InitializeComponent() this.outputText.Multiline = true; this.outputText.Name = "outputText"; this.outputText.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.outputText.Size = new System.Drawing.Size(262, 430); + this.outputText.Size = new System.Drawing.Size(262, 416); this.outputText.TabIndex = 3; // // disconnectButton @@ -376,6 +449,8 @@ private void InitializeComponent() tabColors.ResumeLayout(false); tabColors.PerformLayout(); tabs.ResumeLayout(false); + this.tabDetection.ResumeLayout(false); + this.tabDetection.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.bedPicture)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -398,5 +473,10 @@ private void InitializeComponent() private System.Windows.Forms.TextBox widthText; private System.Windows.Forms.TextBox maxZText; private System.Windows.Forms.TextBox minZText; + private System.Windows.Forms.TabPage tabDetection; + private System.Windows.Forms.CheckBox checkCustom; + private System.Windows.Forms.CheckBox checkMarlin2; + private System.Windows.Forms.CheckBox checkMarlin1; + private System.Windows.Forms.RichTextBox textCustom; } } \ No newline at end of file diff --git a/BedLeveler.cs b/BedLeveler.cs index 9fc5480..8304aec 100644 --- a/BedLeveler.cs +++ b/BedLeveler.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Drawing.Drawing2D; using System.Drawing; +using System.Text.RegularExpressions; namespace BedLeveler { @@ -14,10 +15,18 @@ public partial class BedLeveler : Form readonly List points = new List(); readonly List toMeasure = new List(); + readonly Regex Marlin1 = ParseSimplifiedDetector("Bed Position X: {X} Y: {Y} Z: {Z}"); + readonly Regex Marlin2 = ParseSimplifiedDetector("Bed X: {X} Y: {Y} Z: {Z}"); + Regex customDetection = null; + public BedLeveler() { InitializeComponent(); - Text += $" {Application.ProductVersion}"; + + var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; + Text += $" {version.Major}.{version.Minor}"; + + UpdateCustomDetection(textCustom); // Give our best guess at a port name portText.Text = System.IO.Ports.SerialPort.GetPortNames().FirstOrDefault() ?? "COM5"; @@ -44,6 +53,51 @@ public BedLeveler() } } + private static Regex ParseSimplifiedDetector(string s) + { + const string matchFloat = @"[-+]?[0-9]*\.?[0-9]+"; + + // The curly braces happen to be a regex-special character and + // the escaping is weird, so we use our own escaping scheme + string expr = Regex.Escape(s.ReplaceFirst("{X}", "__X__").ReplaceFirst("{Y}", "__Y__").ReplaceFirst("{Z}", "__Z__")); + expr = expr.ReplaceFirst("__X__", $"(?{matchFloat})"); + expr = expr.ReplaceFirst("__Y__", $"(?{matchFloat})"); + expr = expr.ReplaceFirst("__Z__", $"(?{matchFloat})"); + + Regex r = new Regex(expr); + return r.GetGroupNumbers().Length == 4 ? r : null; + } + + private void UpdateCustomDetection(RichTextBox box) + { + box.MinimalImpactFormat((t) => + { + t.SelectAll(); + t.SelectionColor = SystemColors.WindowText; + t.SelectionFont = t.Font; + + var bold = new Font(t.Font, FontStyle.Bold); + void highlightFirst(string s, Color c) + { + var found = t.Text.IndexOf(s); + if (found == -1) return; + + t.Select(found, s.Length); + t.SelectionColor = c; + t.SelectionFont = bold; + } + + highlightFirst("{X}", Color.Red); + highlightFirst("{Y}", Color.Green); + highlightFirst("{Z}", Color.Blue); + }); + + Regex r = ParseSimplifiedDetector(box.Text); + bool redraw = (r == null) != (customDetection == null); + customDetection = r; + + if (redraw) bedPicture.Invalidate(); + } (float?, float?) ColorBounds { @@ -82,26 +136,37 @@ private void MeasureNextPoint() private void DataReceived(string s) { - if (s.StartsWith("ok") && s.Length < 4) return; + // Omit some of the more verbose printer chatter + const int ShortestPossibleMessage = 14; + if (s.StartsWith("ok") && s.Length < 2 + ShortestPossibleMessage) return; + if (s.Contains("echo") && s.Contains("busy") && s.Contains("processing") && s.Length < 18 + ShortestPossibleMessage) return; Invoke(new Action(() => { outputText.AppendText(s); outputText.AppendText(Environment.NewLine); outputText.ScrollToCaret(); - // "Bed Position X: 13.00 Y: 10.00 Z: 0.10" - if (!s.StartsWith("Bed Position")) return; + Vector3? Check(Regex r) + { + if (r == null) return null; - var tokens = s.Split(' '); - if (tokens.Length != 8) return; + var m = r.Match(s); + if (!m.Success) return null; - float x = float.Parse(tokens[3]); - float y = float.Parse(tokens[5]); - float z = float.Parse(tokens[7]); - points.Add(new Vector3(x, y, z)); + float x = float.Parse(m.Groups["x"].Value); + float y = float.Parse(m.Groups["y"].Value); + float z = float.Parse(m.Groups["z"].Value); + return new Vector3(x, y, z); + } - MeasureNextPoint(); + Vector3? p = null; + if (!p.HasValue && checkMarlin1.Checked) p = Check(Marlin1); + if (!p.HasValue && checkMarlin2.Checked) p = Check(Marlin2); + if (!p.HasValue && checkCustom.Checked) p = Check(customDetection); + if (!p.HasValue) return; + points.Add(p.Value); + MeasureNextPoint(); bedPicture.Invalidate(); })); } @@ -154,16 +219,19 @@ private void BedPaint(object sender, PaintEventArgs e) g.CompositingQuality = CompositingQuality.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality; - var (w, h) = BedDimensions; - if (w < 15 || h < 15) + void DrawError(string s) { using (Font font = new Font("Arial", 18.0f, FontStyle.Bold)) using (StringFormat format = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }) - g.DrawString("Enter valid bed width and height!", font, Brushes.Red, bedPicture.ClientRectangle, format); - - return; + g.DrawString(s, font, Brushes.Red, bedPicture.ClientRectangle, format); } + var (w, h) = BedDimensions; + if (w < 15 || h < 15) { DrawError("Enter valid bed width and height!"); return; } + + if (checkCustom.Checked && customDetection == null) { DrawError("Make sure your custom detection string contains {X}, {Y}, and {Z} somewhere."); return; } + if (!checkMarlin1.Checked && !checkMarlin2.Checked && !checkCustom.Checked) { DrawError("Choose at least one detection method."); return; } + if (points.Count == 0) return; float min = (from p in points select p.Z).Min(); @@ -282,5 +350,16 @@ private void RedrawBedImage(object sender, EventArgs e) { bedPicture.Invalidate(); } + + private void customDetectionChanged(object sender, EventArgs e) + { + UpdateCustomDetection(textCustom); + } + + private void checkCustomChanged(object sender, EventArgs e) + { + textCustom.Enabled = checkCustom.Checked; + bedPicture.Invalidate(); + } } } diff --git a/BedLeveler.csproj b/BedLeveler.csproj index 3b91145..5bc0a5d 100644 --- a/BedLeveler.csproj +++ b/BedLeveler.csproj @@ -57,6 +57,7 @@ + BedLeveler.cs diff --git a/BedLeveler.resx b/BedLeveler.resx index cf952fa..6b9658b 100644 --- a/BedLeveler.resx +++ b/BedLeveler.resx @@ -135,6 +135,18 @@ False + + False + + + False + + + False + + + False + False @@ -153,6 +165,21 @@ False + + False + + + False + + + False + + + False + + + False + False diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 2bc2e95..f080187 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BedLeveler")] -[assembly: AssemblyCopyright("Copyright ©2018-2019 Nicholas Piegdon")] +[assembly: AssemblyCopyright("Copyright ©2018-2020 Nicholas Piegdon")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.0")] +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/WindowsFormsExtensions.cs b/WindowsFormsExtensions.cs new file mode 100644 index 0000000..a51f158 --- /dev/null +++ b/WindowsFormsExtensions.cs @@ -0,0 +1,64 @@ +using System.Drawing; +using System.Runtime.InteropServices; + +namespace System.Windows.Forms +{ + public static class ControlExtensions + { + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam); + + /// + /// Prevents a control from redrawing. This is useful when performing bulk updates. + /// + public static void SetRedraw(this Control control, bool allow) + { + const int WM_SETREDRAW = 0xB; + SendMessage(control.Handle, WM_SETREDRAW, allow ? 1 : 0, IntPtr.Zero); + } + + /// + /// Allows any amount of updating/formatting while handling the details of preserving + /// the current position and selection in the box (while also preventing flicker). + /// + public static void MinimalImpactFormat(this RichTextBox box, Action f) + { + // Grab previous state + var (prevStart, prevLen) = (box.SelectionStart, box.SelectionLength); + var leftId = box.GetCharIndexFromPosition(new Point(3, 3)); + var rightId = box.GetCharIndexFromPosition(new Point(box.Width - 3, box.Height - 3)); + + // Prevent flicker + box.SetRedraw(false); + + f(box); + + // Reset our view + box.SelectionLength = 0; + box.SelectionStart = 0; + + // Restore the previous view + box.SelectionStart = leftId; + box.SelectionLength = Math.Max(0, rightId - leftId); + + // Restore the previous selection + box.SelectionLength = prevLen; + box.SelectionStart = prevStart; + + // Resume drawing and force a redraw + box.SetRedraw(true); + box.Invalidate(); + } + } + + public static class SystemExtensions + { + public static string ReplaceFirst(this string text, string search, string replace) + { + int pos = text.IndexOf(search); + if (pos < 0) return text; + + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + } +}