feat: add direction arrows and reverse direction to program editor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 21:37:25 -04:00
parent 3da5d1c70c
commit 24b89689c5
2 changed files with 151 additions and 0 deletions

View File

@@ -140,4 +140,18 @@ public class ContourClassificationTests
Assert.Equal(ContourClassification.Perimeter, contours[0].Type);
Assert.Equal("Perimeter", contours[0].Label);
}
[Fact]
public void Reverse_changes_direction_label()
{
var shape = MakeRectShape(0, 0, 100, 50);
var contours = ContourInfo.Classify(new List<Shape> { shape });
var contour = contours[0];
var originalDirection = contour.DirectionLabel;
contour.Reverse();
var newDirection = contour.DirectionLabel;
Assert.NotEqual(originalDirection, newDirection);
}
}

View File

@@ -22,6 +22,8 @@ namespace OpenNest.Controls
contourList.DrawItem += OnDrawContourItem;
contourList.MeasureItem += OnMeasureContourItem;
contourList.SelectedIndexChanged += OnContourSelectionChanged;
reverseButton.Click += OnReverseClicked;
menuReverse.Click += OnReverseClicked;
}
public Program Program { get; private set; }
@@ -101,6 +103,9 @@ namespace OpenNest.Controls
entity.Color = color;
preview.Entities.Add(entity);
}
if (selected)
AddDirectionArrows(contour.Shape, color);
}
preview.ZoomToFit();
@@ -178,5 +183,137 @@ namespace OpenNest.Controls
{
RefreshPreview();
}
private void OnReverseClicked(object sender, EventArgs e)
{
if (contourList.SelectedIndices.Count == 0) return;
foreach (int index in contourList.SelectedIndices)
{
if (index >= 0 && index < contours.Count)
contours[index].Reverse();
}
Program = BuildProgram(contours);
isDirty = true;
contourList.Invalidate();
UpdateGcodeText();
RefreshPreview();
ProgramChanged?.Invoke(this, EventArgs.Empty);
}
private void AddDirectionArrows(Shape shape, Color color)
{
var entities = shape.Entities;
if (entities.Count == 0) return;
var totalLength = shape.Length;
if (totalLength < 0.001) return;
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 })
{
var targetDist = totalLength * fraction;
var accumulated = 0.0;
var found = false;
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;
}
if (!found && entities.Count > 0)
{
var last = entities[^1];
var (point, angle) = GetPointAndAngle(last, 0.5);
AddArrowHead(point, angle, arrowSize, color);
}
}
}
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 });
}
}
}