feat: add SimplifierViewerForm tool window
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,223 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using OpenNest.Controls;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.Forms;
|
||||||
|
|
||||||
|
public class SimplifierViewerForm : Form
|
||||||
|
{
|
||||||
|
private ListView listView;
|
||||||
|
private System.Windows.Forms.NumericUpDown numTolerance;
|
||||||
|
private Label lblCount;
|
||||||
|
private Button btnApply;
|
||||||
|
private EntityView entityView;
|
||||||
|
private GeometrySimplifier simplifier;
|
||||||
|
private List<Shape> shapes;
|
||||||
|
private List<ArcCandidate> candidates;
|
||||||
|
|
||||||
|
public event System.Action<List<Entity>> Applied;
|
||||||
|
|
||||||
|
public SimplifierViewerForm()
|
||||||
|
{
|
||||||
|
Text = "Geometry Simplifier";
|
||||||
|
FormBorderStyle = FormBorderStyle.SizableToolWindow;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
TopMost = true;
|
||||||
|
StartPosition = FormStartPosition.Manual;
|
||||||
|
Size = new System.Drawing.Size(420, 450);
|
||||||
|
Font = new Font("Segoe UI", 9f);
|
||||||
|
|
||||||
|
InitializeControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeControls()
|
||||||
|
{
|
||||||
|
// Bottom panel
|
||||||
|
var bottomPanel = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Bottom,
|
||||||
|
Height = 36,
|
||||||
|
Padding = new Padding(4, 6, 4, 4),
|
||||||
|
WrapContents = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
var lblTolerance = new Label
|
||||||
|
{
|
||||||
|
Text = "Tolerance:",
|
||||||
|
AutoSize = true,
|
||||||
|
Margin = new Padding(0, 3, 2, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
numTolerance = new System.Windows.Forms.NumericUpDown
|
||||||
|
{
|
||||||
|
Minimum = 0.001m,
|
||||||
|
Maximum = 1.000m,
|
||||||
|
DecimalPlaces = 3,
|
||||||
|
Increment = 0.001m,
|
||||||
|
Value = 0.005m,
|
||||||
|
Width = 70,
|
||||||
|
};
|
||||||
|
numTolerance.ValueChanged += OnToleranceChanged;
|
||||||
|
|
||||||
|
lblCount = new Label
|
||||||
|
{
|
||||||
|
Text = "0 of 0 selected",
|
||||||
|
AutoSize = true,
|
||||||
|
Margin = new Padding(8, 3, 4, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
btnApply = new Button
|
||||||
|
{
|
||||||
|
Text = "Apply",
|
||||||
|
FlatStyle = FlatStyle.Flat,
|
||||||
|
Width = 60,
|
||||||
|
Margin = new Padding(4, 0, 0, 0),
|
||||||
|
};
|
||||||
|
btnApply.Click += OnApplyClick;
|
||||||
|
|
||||||
|
bottomPanel.Controls.AddRange(new Control[] { lblTolerance, numTolerance, lblCount, btnApply });
|
||||||
|
|
||||||
|
// ListView
|
||||||
|
listView = new ListView
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
View = View.Details,
|
||||||
|
FullRowSelect = true,
|
||||||
|
CheckBoxes = true,
|
||||||
|
GridLines = true,
|
||||||
|
};
|
||||||
|
listView.Columns.Add("Lines", 50);
|
||||||
|
listView.Columns.Add("Radius", 70);
|
||||||
|
listView.Columns.Add("Deviation", 75);
|
||||||
|
listView.Columns.Add("Location", 100);
|
||||||
|
listView.ItemSelectionChanged += OnItemSelected;
|
||||||
|
listView.ItemChecked += OnItemChecked;
|
||||||
|
|
||||||
|
Controls.Add(listView);
|
||||||
|
Controls.Add(bottomPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadShapes(List<Shape> shapes, EntityView view, double tolerance = 0.005)
|
||||||
|
{
|
||||||
|
this.shapes = shapes;
|
||||||
|
this.entityView = view;
|
||||||
|
numTolerance.Value = (decimal)tolerance;
|
||||||
|
simplifier = new GeometrySimplifier { Tolerance = tolerance };
|
||||||
|
RunAnalysis();
|
||||||
|
Show();
|
||||||
|
BringToFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunAnalysis()
|
||||||
|
{
|
||||||
|
candidates = new List<ArcCandidate>();
|
||||||
|
for (var i = 0; i < shapes.Count; i++)
|
||||||
|
{
|
||||||
|
var shapeCandidates = simplifier.Analyze(shapes[i]);
|
||||||
|
foreach (var c in shapeCandidates)
|
||||||
|
c.ShapeIndex = i;
|
||||||
|
candidates.AddRange(shapeCandidates);
|
||||||
|
}
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshList()
|
||||||
|
{
|
||||||
|
listView.BeginUpdate();
|
||||||
|
listView.Items.Clear();
|
||||||
|
|
||||||
|
foreach (var c in candidates)
|
||||||
|
{
|
||||||
|
var item = new ListViewItem(c.LineCount.ToString());
|
||||||
|
item.Checked = c.IsSelected;
|
||||||
|
item.SubItems.Add(c.FittedArc.Radius.ToString("F3"));
|
||||||
|
item.SubItems.Add(c.MaxDeviation.ToString("F4"));
|
||||||
|
item.SubItems.Add($"{c.BoundingBox.Center.X:F1}, {c.BoundingBox.Center.Y:F1}");
|
||||||
|
item.Tag = c;
|
||||||
|
listView.Items.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
listView.EndUpdate();
|
||||||
|
UpdateCountLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCountLabel()
|
||||||
|
{
|
||||||
|
var selected = candidates.Count(c => c.IsSelected);
|
||||||
|
lblCount.Text = $"{selected} of {candidates.Count} selected";
|
||||||
|
btnApply.Enabled = selected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemSelected(object sender, ListViewItemSelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.IsSelected || e.Item.Tag is not ArcCandidate candidate)
|
||||||
|
{
|
||||||
|
entityView?.ClearSimplifierPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the candidate lines in the shape
|
||||||
|
var shape = shapes[candidate.ShapeIndex];
|
||||||
|
var highlightEntities = new List<Entity>();
|
||||||
|
for (var i = candidate.StartIndex; i <= candidate.EndIndex; i++)
|
||||||
|
highlightEntities.Add(shape.Entities[i]);
|
||||||
|
|
||||||
|
entityView.SimplifierHighlight = highlightEntities;
|
||||||
|
entityView.SimplifierPreview = candidate.FittedArc;
|
||||||
|
entityView.ZoomToArea(candidate.BoundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemChecked(object sender, ItemCheckedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Item.Tag is ArcCandidate candidate)
|
||||||
|
{
|
||||||
|
candidate.IsSelected = e.Item.Checked;
|
||||||
|
UpdateCountLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToleranceChanged(object sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
simplifier.Tolerance = (double)numTolerance.Value;
|
||||||
|
entityView?.ClearSimplifierPreview();
|
||||||
|
RunAnalysis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplyClick(object sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
var byShape = candidates
|
||||||
|
.Where(c => c.IsSelected)
|
||||||
|
.GroupBy(c => c.ShapeIndex)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
for (var i = 0; i < shapes.Count; i++)
|
||||||
|
{
|
||||||
|
if (byShape.TryGetValue(i, out var selected))
|
||||||
|
shapes[i] = simplifier.Apply(shapes[i], selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entities = shapes.SelectMany(s => s.Entities).ToList();
|
||||||
|
entityView?.ClearSimplifierPreview();
|
||||||
|
Applied?.Invoke(entities);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ProcessDialogKey(Keys keyData)
|
||||||
|
{
|
||||||
|
if (keyData == Keys.Escape)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return base.ProcessDialogKey(keyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
entityView?.ClearSimplifierPreview();
|
||||||
|
base.OnFormClosing(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user