From 3da5d1c70cb81815bbe64d58e25ff2ecc6d4a333 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 31 Mar 2026 21:35:07 -0400 Subject: [PATCH] feat: implement contour list display and entity loading Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest/Controls/ProgramEditorControl.cs | 138 +++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/OpenNest/Controls/ProgramEditorControl.cs b/OpenNest/Controls/ProgramEditorControl.cs index 3480b3b..a5a2d73 100644 --- a/OpenNest/Controls/ProgramEditorControl.cs +++ b/OpenNest/Controls/ProgramEditorControl.cs @@ -18,6 +18,10 @@ namespace OpenNest.Controls public ProgramEditorControl() { InitializeComponent(); + + contourList.DrawItem += OnDrawContourItem; + contourList.MeasureItem += OnMeasureContourItem; + contourList.SelectedIndexChanged += OnContourSelectionChanged; } public Program Program { get; private set; } @@ -28,7 +32,21 @@ namespace OpenNest.Controls public void LoadEntities(List entities) { - // Will be implemented in Task 3 + var shapes = ShapeBuilder.GetShapes(entities); + if (shapes.Count == 0) + { + Clear(); + return; + } + + contours = ContourInfo.Classify(shapes); + Program = BuildProgram(contours); + isDirty = false; + isLoaded = true; + + PopulateContourList(); + UpdateGcodeText(); + RefreshPreview(); } public void Clear() @@ -42,5 +60,123 @@ namespace OpenNest.Controls isDirty = false; isLoaded = false; } + + private static Program BuildProgram(List contours) + { + var pgm = new Program(); + foreach (var contour in contours) + { + var sub = ConvertGeometry.ToProgram(contour.Shape); + pgm.Merge(sub); + } + pgm.Mode = Mode.Incremental; + return pgm; + } + + private void PopulateContourList() + { + contourList.Items.Clear(); + foreach (var contour in contours) + contourList.Items.Add(contour); + } + + private void UpdateGcodeText() + { + gcodeEditor.Text = Program?.ToString() ?? string.Empty; + } + + private void RefreshPreview() + { + preview.ClearPenCache(); + preview.Entities.Clear(); + + for (var i = 0; i < contours.Count; i++) + { + var contour = contours[i]; + var selected = contourList.SelectedIndices.Contains(i); + var color = GetContourColor(contour.Type, selected); + + foreach (var entity in contour.Shape.Entities) + { + entity.Color = color; + preview.Entities.Add(entity); + } + } + + preview.ZoomToFit(); + preview.Invalidate(); + } + + private static Color GetContourColor(ContourClassification type, bool selected) + { + if (selected) + return Color.White; + + return type switch + { + ContourClassification.Perimeter => Color.FromArgb(80, 180, 120), + ContourClassification.Hole => Color.FromArgb(100, 140, 255), + ContourClassification.Etch => Color.FromArgb(255, 170, 50), + ContourClassification.Open => Color.FromArgb(200, 200, 100), + _ => Color.Gray, + }; + } + + private void OnMeasureContourItem(object sender, MeasureItemEventArgs e) + { + e.ItemHeight = 42; + } + + private void OnDrawContourItem(object sender, DrawItemEventArgs e) + { + if (e.Index < 0 || e.Index >= contours.Count) return; + + var contour = contours[e.Index]; + var selected = (e.State & DrawItemState.Selected) != 0; + var bounds = e.Bounds; + + // Background + using var bgBrush = new SolidBrush(selected + ? Color.FromArgb(230, 238, 255) + : Color.White); + e.Graphics.FillRectangle(bgBrush, bounds); + + // Accent bar + var accentColor = GetContourColor(contour.Type, false); + using var accentBrush = new SolidBrush(accentColor); + e.Graphics.FillRectangle(accentBrush, bounds.X, bounds.Y + 4, 3, bounds.Height - 8); + + // Direction icon + var icon = contour.Type switch + { + ContourClassification.Perimeter or ContourClassification.Hole => + contour.DirectionLabel == "CW" ? "\u21BB" : "\u21BA", + ContourClassification.Etch => "\u2014", + _ => "\u2014", + }; + using var iconFont = new Font("Segoe UI", 14f); + using var iconBrush = new SolidBrush(accentColor); + e.Graphics.DrawString(icon, iconFont, iconBrush, bounds.X + 8, bounds.Y + 6); + + // Label + using var labelFont = new Font("Segoe UI", 9f, FontStyle.Bold); + using var labelBrush = new SolidBrush(Color.FromArgb(40, 40, 40)); + e.Graphics.DrawString(contour.Label, labelFont, labelBrush, bounds.X + 32, bounds.Y + 4); + + // Info line + var info = $"{contour.DirectionLabel} \u00B7 {contour.DimensionLabel}"; + using var infoFont = new Font("Segoe UI", 8f); + using var infoBrush = new SolidBrush(Color.Gray); + e.Graphics.DrawString(info, infoFont, infoBrush, bounds.X + 32, bounds.Y + 22); + + // Separator + using var sepPen = new Pen(Color.FromArgb(230, 230, 230)); + e.Graphics.DrawLine(sepPen, bounds.X + 8, bounds.Bottom - 1, bounds.Right - 8, bounds.Bottom - 1); + } + + private void OnContourSelectionChanged(object sender, EventArgs e) + { + RefreshPreview(); + } } }