From bb70ae26d3e8faefe499c80ecca7a167b3405d63 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 31 Mar 2026 22:22:57 -0400 Subject: [PATCH] refactor: extract CutDirectionArrows and reuse in program editor preview --- OpenNest/Controls/CutDirectionArrows.cs | 146 ++++++++++++++++++++++ OpenNest/Controls/PlateRenderer.cs | 137 +------------------- OpenNest/Controls/ProgramEditorControl.cs | 118 ++--------------- 3 files changed, 160 insertions(+), 241 deletions(-) create mode 100644 OpenNest/Controls/CutDirectionArrows.cs diff --git a/OpenNest/Controls/CutDirectionArrows.cs b/OpenNest/Controls/CutDirectionArrows.cs new file mode 100644 index 0000000..e3aa87d --- /dev/null +++ b/OpenNest/Controls/CutDirectionArrows.cs @@ -0,0 +1,146 @@ +using OpenNest.CNC; +using OpenNest.Geometry; +using OpenNest.Math; +using System; +using System.Drawing; + +namespace OpenNest.Controls +{ + internal static class CutDirectionArrows + { + public static void DrawProgram(Graphics g, DrawControl view, Program pgm, ref Vector pos, + Pen pen, double spacing, float arrowSize) + { + for (var i = 0; i < pgm.Length; ++i) + { + var code = pgm[i]; + + if (code.Type == CodeType.SubProgramCall) + { + var subpgm = (SubProgramCall)code; + if (subpgm.Program != null) + DrawProgram(g, view, subpgm.Program, ref pos, pen, spacing, arrowSize); + continue; + } + + if (code is not Motion motion) continue; + + var endpt = pgm.Mode == Mode.Incremental + ? motion.EndPoint + pos + : motion.EndPoint; + + if (code.Type == CodeType.LinearMove) + { + var line = (LinearMove)code; + if (!line.Suppressed) + DrawLineArrows(g, view, pos, endpt, pen, spacing, arrowSize); + } + else if (code.Type == CodeType.ArcMove) + { + var arc = (ArcMove)code; + if (!arc.Suppressed) + { + var center = pgm.Mode == Mode.Incremental + ? arc.CenterPoint + pos + : arc.CenterPoint; + DrawArcArrows(g, view, pos, endpt, center, arc.Rotation, pen, spacing, arrowSize); + } + } + + pos = endpt; + } + } + + private static void DrawLineArrows(Graphics g, DrawControl view, Vector start, Vector end, + Pen pen, double spacing, float arrowSize) + { + var dx = end.X - start.X; + var dy = end.Y - start.Y; + var length = System.Math.Sqrt(dx * dx + dy * dy); + if (length < spacing * 0.5) return; + + var dirX = dx / length; + var dirY = dy / length; + + var count = System.Math.Max(1, (int)(length / spacing)); + var step = length / (count + 1); + + for (var i = 1; i <= count; i++) + { + var t = step * i; + var pt = new Vector(start.X + dirX * t, start.Y + dirY * t); + var screenPt = view.PointWorldToGraph(pt); + var angle = System.Math.Atan2(-dirY, dirX); + DrawArrowHead(g, pen, screenPt, angle, arrowSize); + } + } + + private static void DrawArcArrows(Graphics g, DrawControl view, Vector start, Vector end, Vector center, + RotationType rotation, Pen pen, double spacing, float arrowSize) + { + var radius = center.DistanceTo(start); + if (radius < Tolerance.Epsilon) return; + + var startAngle = System.Math.Atan2(start.Y - center.Y, start.X - center.X); + var endAngle = System.Math.Atan2(end.Y - center.Y, end.X - center.X); + + double sweep; + if (rotation == RotationType.CCW) + { + sweep = endAngle - startAngle; + if (sweep <= 0) sweep += 2 * System.Math.PI; + } + else + { + sweep = startAngle - endAngle; + if (sweep <= 0) sweep += 2 * System.Math.PI; + } + + var arcLength = radius * System.Math.Abs(sweep); + if (arcLength < spacing * 0.5) return; + + var count = System.Math.Max(1, (int)(arcLength / spacing)); + var stepAngle = sweep / (count + 1); + + for (var i = 1; i <= count; i++) + { + double angle; + if (rotation == RotationType.CCW) + angle = startAngle + stepAngle * i; + else + angle = startAngle - stepAngle * i; + + var pt = new Vector( + center.X + radius * System.Math.Cos(angle), + center.Y + radius * System.Math.Sin(angle)); + var screenPt = view.PointWorldToGraph(pt); + + double tangent; + if (rotation == RotationType.CCW) + tangent = angle + System.Math.PI / 2; + else + tangent = angle - System.Math.PI / 2; + + var screenAngle = System.Math.Atan2(-System.Math.Sin(tangent), System.Math.Cos(tangent)); + DrawArrowHead(g, pen, screenPt, screenAngle, arrowSize); + } + } + + private static void DrawArrowHead(Graphics g, Pen pen, PointF tip, double angle, float size) + { + var sin = (float)System.Math.Sin(angle); + var cos = (float)System.Math.Cos(angle); + + var backX = -size * cos; + var backY = -size * sin; + var wingX = size * 0.5f * sin; + var wingY = -size * 0.5f * cos; + + var wing1 = new PointF(tip.X + backX + wingX, tip.Y + backY + wingY); + var wing2 = new PointF(tip.X + backX - wingX, tip.Y + backY - wingY); + + g.DrawLine(pen, wing1, tip); + g.DrawLine(pen, wing2, tip); + } + } +} diff --git a/OpenNest/Controls/PlateRenderer.cs b/OpenNest/Controls/PlateRenderer.cs index 9643f35..9baf863 100644 --- a/OpenNest/Controls/PlateRenderer.cs +++ b/OpenNest/Controls/PlateRenderer.cs @@ -525,145 +525,10 @@ namespace OpenNest.Controls var part = view.Plate.Parts[i]; var pgm = part.Program; var pos = part.Location; - DrawProgramCutDirectionArrows(g, pgm, ref pos, pen, arrowSpacingWorld, arrowSize); + CutDirectionArrows.DrawProgram(g, view, pgm, ref pos, pen, arrowSpacingWorld, arrowSize); } } - private void DrawProgramCutDirectionArrows(Graphics g, Program pgm, ref Vector pos, - Pen pen, double spacing, float arrowSize) - { - for (var i = 0; i < pgm.Length; ++i) - { - var code = pgm[i]; - - if (code.Type == CodeType.SubProgramCall) - { - var subpgm = (SubProgramCall)code; - if (subpgm.Program != null) - DrawProgramCutDirectionArrows(g, subpgm.Program, ref pos, pen, spacing, arrowSize); - continue; - } - - if (code is not Motion motion) continue; - - var endpt = pgm.Mode == Mode.Incremental - ? motion.EndPoint + pos - : motion.EndPoint; - - if (code.Type == CodeType.LinearMove) - { - var line = (LinearMove)code; - if (!line.Suppressed) - DrawLineDirectionArrows(g, pos, endpt, pen, spacing, arrowSize); - } - else if (code.Type == CodeType.ArcMove) - { - var arc = (ArcMove)code; - if (!arc.Suppressed) - { - var center = pgm.Mode == Mode.Incremental - ? arc.CenterPoint + pos - : arc.CenterPoint; - DrawArcDirectionArrows(g, pos, endpt, center, arc.Rotation, pen, spacing, arrowSize); - } - } - - pos = endpt; - } - } - - private void DrawLineDirectionArrows(Graphics g, Vector start, Vector end, - Pen pen, double spacing, float arrowSize) - { - var dx = end.X - start.X; - var dy = end.Y - start.Y; - var length = System.Math.Sqrt(dx * dx + dy * dy); - if (length < spacing * 0.5) return; - - var dirX = dx / length; - var dirY = dy / length; - - var count = System.Math.Max(1, (int)(length / spacing)); - var step = length / (count + 1); - - for (var i = 1; i <= count; i++) - { - var t = step * i; - var pt = new Vector(start.X + dirX * t, start.Y + dirY * t); - var screenPt = view.PointWorldToGraph(pt); - var angle = System.Math.Atan2(-dirY, dirX); - DrawArrowHead(g, pen, screenPt, angle, arrowSize); - } - } - - private void DrawArcDirectionArrows(Graphics g, Vector start, Vector end, Vector center, - RotationType rotation, Pen pen, double spacing, float arrowSize) - { - var radius = center.DistanceTo(start); - if (radius < Tolerance.Epsilon) return; - - var startAngle = System.Math.Atan2(start.Y - center.Y, start.X - center.X); - var endAngle = System.Math.Atan2(end.Y - center.Y, end.X - center.X); - - double sweep; - if (rotation == RotationType.CCW) - { - sweep = endAngle - startAngle; - if (sweep <= 0) sweep += 2 * System.Math.PI; - } - else - { - sweep = startAngle - endAngle; - if (sweep <= 0) sweep += 2 * System.Math.PI; - } - - var arcLength = radius * System.Math.Abs(sweep); - if (arcLength < spacing * 0.5) return; - - var count = System.Math.Max(1, (int)(arcLength / spacing)); - var stepAngle = sweep / (count + 1); - - for (var i = 1; i <= count; i++) - { - double angle; - if (rotation == RotationType.CCW) - angle = startAngle + stepAngle * i; - else - angle = startAngle - stepAngle * i; - - var pt = new Vector( - center.X + radius * System.Math.Cos(angle), - center.Y + radius * System.Math.Sin(angle)); - var screenPt = view.PointWorldToGraph(pt); - - double tangent; - if (rotation == RotationType.CCW) - tangent = angle + System.Math.PI / 2; - else - tangent = angle - System.Math.PI / 2; - - var screenAngle = System.Math.Atan2(-System.Math.Sin(tangent), System.Math.Cos(tangent)); - DrawArrowHead(g, pen, screenPt, screenAngle, arrowSize); - } - } - - private static void DrawArrowHead(Graphics g, Pen pen, PointF tip, double angle, float size) - { - var sin = (float)System.Math.Sin(angle); - var cos = (float)System.Math.Cos(angle); - - var backX = -size * cos; - var backY = -size * sin; - var wingX = size * 0.5f * sin; - var wingY = -size * 0.5f * cos; - - var wing1 = new PointF(tip.X + backX + wingX, tip.Y + backY + wingY); - var wing2 = new PointF(tip.X + backX - wingX, tip.Y + backY - wingY); - - g.DrawLine(pen, wing1, tip); - g.DrawLine(pen, wing2, tip); - } - private void DrawLine(Graphics g, Vector pt1, Vector pt2, Pen pen) { var point1 = view.PointWorldToGraph(pt1); diff --git a/OpenNest/Controls/ProgramEditorControl.cs b/OpenNest/Controls/ProgramEditorControl.cs index d58f7ed..d185806 100644 --- a/OpenNest/Controls/ProgramEditorControl.cs +++ b/OpenNest/Controls/ProgramEditorControl.cs @@ -26,6 +26,7 @@ namespace OpenNest.Controls reverseButton.Click += OnReverseClicked; menuReverse.Click += OnReverseClicked; applyButton.Click += OnApplyClicked; + preview.Paint += OnPreviewPaint; } public Program Program { get; private set; } @@ -157,7 +158,6 @@ namespace OpenNest.Controls var selColor = GetContourColor(contour.Type, true); foreach (var entity in contour.Shape.Entities) entity.Color = selColor; - AddDirectionArrows(contour.Shape, selColor); } preview.Entities.AddRange(contour.Shape.Entities); @@ -258,119 +258,27 @@ namespace OpenNest.Controls ProgramChanged?.Invoke(this, EventArgs.Empty); } - private void AddDirectionArrows(Shape shape, Color color) + private void OnPreviewPaint(object sender, PaintEventArgs e) { - var entities = shape.Entities; - if (entities.Count == 0) return; + if (contours.Count == 0) return; - var totalLength = shape.Length; - if (totalLength < 0.001) return; + var spacing = preview.LengthGuiToWorld(60f); + var arrowSize = 5f; - var arrowSize = totalLength * 0.02; - if (arrowSize < 0.5) arrowSize = 0.5; - if (arrowSize > 5) arrowSize = 5; - - foreach (var fraction in new[] { 0.25, 0.75 }) + for (var i = 0; i < contours.Count; i++) { - var targetDist = totalLength * fraction; - var accumulated = 0.0; - var found = false; + if (!contourList.SelectedIndices.Contains(i)) continue; - foreach (var entity in entities) - { - var entityLen = entity.Length; - if (accumulated + entityLen >= targetDist) - { - var localFraction = (targetDist - accumulated) / entityLen; - var (point, angle) = GetPointAndAngle(entity, localFraction); - AddArrowHead(point, angle, arrowSize, color); - found = true; - break; - } - accumulated += entityLen; - } + var contour = contours[i]; + var pgm = ConvertGeometry.ToProgram(contour.Shape); + if (pgm == null) continue; - if (!found && entities.Count > 0) - { - var last = entities[^1]; - var (point, angle) = GetPointAndAngle(last, 0.5); - AddArrowHead(point, angle, arrowSize, color); - } + using var pen = new Pen(Color.FromArgb(220, Color.Black), 1.5f); + var pos = new Vector(); + CutDirectionArrows.DrawProgram(e.Graphics, preview, pgm, ref pos, pen, spacing, arrowSize); } } - private static (Vector point, double angle) GetPointAndAngle(Entity entity, double fraction) - { - switch (entity) - { - case Line line: - { - var dx = line.EndPoint.X - line.StartPoint.X; - var dy = line.EndPoint.Y - line.StartPoint.Y; - var pt = new Vector( - line.StartPoint.X + dx * fraction, - line.StartPoint.Y + dy * fraction); - var angle = System.Math.Atan2(dy, dx); - return (pt, angle); - } - case Arc arc: - { - var startAngle = arc.StartAngle; - var endAngle = arc.EndAngle; - if (arc.IsReversed) - { - var span = startAngle - endAngle; - if (span < 0) span += 2 * System.Math.PI; - var a = startAngle - span * fraction; - var pt = new Vector( - arc.Center.X + arc.Radius * System.Math.Cos(a), - arc.Center.Y + arc.Radius * System.Math.Sin(a)); - var tangent = a - System.Math.PI / 2; - return (pt, tangent); - } - else - { - var span = endAngle - startAngle; - if (span < 0) span += 2 * System.Math.PI; - var a = startAngle + span * fraction; - var pt = new Vector( - arc.Center.X + arc.Radius * System.Math.Cos(a), - arc.Center.Y + arc.Radius * System.Math.Sin(a)); - var tangent = a + System.Math.PI / 2; - return (pt, tangent); - } - } - case Circle circle: - { - var a = 2 * System.Math.PI * fraction; - var pt = new Vector( - circle.Center.X + circle.Radius * System.Math.Cos(a), - circle.Center.Y + circle.Radius * System.Math.Sin(a)); - var tangent = a + System.Math.PI / 2; - return (pt, tangent); - } - default: - return (new Vector(), 0); - } - } - - private void AddArrowHead(Vector tip, double angle, double size, Color color) - { - var leftAngle = angle + System.Math.PI + 0.4; - var rightAngle = angle + System.Math.PI - 0.4; - - var left = new Vector( - tip.X + size * System.Math.Cos(leftAngle), - tip.Y + size * System.Math.Sin(leftAngle)); - var right = new Vector( - tip.X + size * System.Math.Cos(rightAngle), - tip.Y + size * System.Math.Sin(rightAngle)); - - var arrowColor = Color.FromArgb(255, 140, 50); - preview.Entities.Add(new Line(left, tip) { Color = arrowColor }); - preview.Entities.Add(new Line(right, tip) { Color = arrowColor }); - } - private void OnApplyClicked(object sender, EventArgs e) { var text = gcodeEditor.Text;