feat(ui): show both horizontal and vertical fill previews side by side

Replace single preview PlateView with two stacked previews showing
horizontal and vertical FillLinear results. Each has a label showing
the part count. Apply uses the direction with more parts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 11:12:24 -04:00
parent a1139efecb
commit a83efd0b01

View File

@@ -25,7 +25,10 @@ namespace OpenNest.Forms
{ {
private readonly Nest nest; private readonly Nest nest;
private readonly PlateView cellView; private readonly PlateView cellView;
private readonly PlateView previewView; private readonly PlateView hPreview;
private readonly PlateView vPreview;
private readonly Label hLabel;
private readonly Label vLabel;
public PatternTileResult Result { get; private set; } public PatternTileResult Result { get; private set; }
@@ -43,14 +46,44 @@ namespace OpenNest.Forms
cellView.Dock = DockStyle.Fill; cellView.Dock = DockStyle.Fill;
splitContainer.Panel1.Controls.Add(cellView); splitContainer.Panel1.Controls.Add(cellView);
// Tile preview — plate visible, read-only // Right side: vertical split with horizontal and vertical preview
previewView = new PlateView(); var previewSplit = new SplitContainer
previewView.Plate.Quantity = 0; // prevent Drawing.Quantity.Nested side-effects {
previewView.AllowSelect = false; Dock = DockStyle.Fill,
previewView.AllowDrop = false; Orientation = Orientation.Horizontal,
previewView.DrawBounds = false; SplitterDistance = 250
previewView.Dock = DockStyle.Fill; };
splitContainer.Panel2.Controls.Add(previewView); splitContainer.Panel2.Controls.Add(previewSplit);
hLabel = new Label
{
Dock = DockStyle.Top,
Height = 20,
Text = "Horizontal — 0 parts",
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
Font = new System.Drawing.Font("Segoe UI", 9f, System.Drawing.FontStyle.Bold),
ForeColor = System.Drawing.Color.FromArgb(80, 80, 80),
Padding = new Padding(4, 0, 0, 0)
};
hPreview = CreatePreviewView();
previewSplit.Panel1.Controls.Add(hPreview);
previewSplit.Panel1.Controls.Add(hLabel);
vLabel = new Label
{
Dock = DockStyle.Top,
Height = 20,
Text = "Vertical — 0 parts",
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
Font = new System.Drawing.Font("Segoe UI", 9f, System.Drawing.FontStyle.Bold),
ForeColor = System.Drawing.Color.FromArgb(80, 80, 80),
Padding = new Padding(4, 0, 0, 0)
};
vPreview = CreatePreviewView();
previewSplit.Panel2.Controls.Add(vPreview);
previewSplit.Panel2.Controls.Add(vLabel);
// Populate drawing dropdowns // Populate drawing dropdowns
var drawings = nest.Drawings.OrderBy(d => d.Name).ToList(); var drawings = nest.Drawings.OrderBy(d => d.Name).ToList();
@@ -192,32 +225,71 @@ namespace OpenNest.Forms
} }
} }
private static PlateView CreatePreviewView()
{
var view = new PlateView();
view.Plate.Quantity = 0;
view.AllowSelect = false;
view.AllowDrop = false;
view.DrawBounds = false;
view.Dock = DockStyle.Fill;
return view;
}
private void UpdatePreviewPlateSize() private void UpdatePreviewPlateSize()
{ {
if (TryGetPlateSize(out var size)) if (!TryGetPlateSize(out var size))
previewView.Plate.Size = size; return;
hPreview.Plate.Size = size;
vPreview.Plate.Size = size;
}
private Pattern BuildCellPattern()
{
var cellParts = cellView.Plate.Parts.ToList();
if (cellParts.Count == 0)
return null;
var pattern = new Pattern();
foreach (var part in cellParts)
pattern.Parts.Add(part);
pattern.UpdateBounds();
return pattern;
} }
private void RebuildPreview() private void RebuildPreview()
{ {
previewView.Plate.Parts.Clear(); hPreview.Plate.Parts.Clear();
vPreview.Plate.Parts.Clear();
if (!TryGetPlateSize(out var plateSize)) if (!TryGetPlateSize(out var plateSize))
return; return;
previewView.Plate.Size = plateSize; hPreview.Plate.Size = plateSize;
previewView.Plate.PartSpacing = PartSpacing; hPreview.Plate.PartSpacing = PartSpacing;
vPreview.Plate.Size = plateSize;
vPreview.Plate.PartSpacing = PartSpacing;
var cellParts = cellView.Plate.Parts.ToList(); var pattern = BuildCellPattern();
if (cellParts.Count == 0) if (pattern == null)
return; return;
var tiled = Engine.PatternTiler.Tile(cellParts, plateSize, PartSpacing); var workArea = new Box(0, 0, plateSize.Width, plateSize.Length);
var filler = new FillLinear(workArea, PartSpacing);
foreach (var part in tiled) var hParts = filler.Fill(pattern, NestDirection.Horizontal);
previewView.Plate.Parts.Add(part); foreach (var part in hParts)
hPreview.Plate.Parts.Add(part);
hLabel.Text = $"Horizontal — {hParts.Count} parts";
hPreview.ZoomToFit();
previewView.ZoomToFit(); var vFiller = new FillLinear(workArea, PartSpacing);
var vParts = vFiller.Fill(pattern, NestDirection.Vertical);
foreach (var part in vParts)
vPreview.Plate.Parts.Add(part);
vLabel.Text = $"Vertical — {vParts.Count} parts";
vPreview.ZoomToFit();
} }
private void OnAutoArrangeClick(object sender, EventArgs e) private void OnAutoArrangeClick(object sender, EventArgs e)
@@ -277,14 +349,29 @@ namespace OpenNest.Forms
private void OnApplyClick(object sender, EventArgs e) private void OnApplyClick(object sender, EventArgs e)
{ {
if (previewView.Plate.Parts.Count == 0) var hCount = hPreview.Plate.Parts.Count;
var vCount = vPreview.Plate.Parts.Count;
if (hCount == 0 && vCount == 0)
return; return;
if (!TryGetPlateSize(out var plateSize)) if (!TryGetPlateSize(out var plateSize))
return; return;
// Pick which direction to apply — use the one with more parts,
// or ask if they're equal and both > 0
NestDirection applyDirection;
if (hCount > vCount)
applyDirection = NestDirection.Horizontal;
else if (vCount > hCount)
applyDirection = NestDirection.Vertical;
else
applyDirection = NestDirection.Horizontal; // tie-break
var choice = MessageBox.Show( var choice = MessageBox.Show(
"Apply pattern to current plate?\n\nYes = Current plate (clears existing parts)\nNo = New plate", $"Apply {applyDirection} pattern ({(applyDirection == NestDirection.Horizontal ? hCount : vCount)} parts) to current plate?" +
"\n\nYes = Current plate (clears existing parts)\nNo = New plate",
"Apply Pattern", "Apply Pattern",
MessageBoxButtons.YesNoCancel, MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question); MessageBoxIcon.Question);
@@ -293,8 +380,12 @@ namespace OpenNest.Forms
return; return;
// Rebuild a fresh set of tiled parts for the caller // Rebuild a fresh set of tiled parts for the caller
var cellParts = cellView.Plate.Parts.ToList(); var pattern = BuildCellPattern();
var tiledParts = Engine.PatternTiler.Tile(cellParts, plateSize, PartSpacing); if (pattern == null)
return;
var filler = new FillLinear(new Box(0, 0, plateSize.Width, plateSize.Length), PartSpacing);
var tiledParts = filler.Fill(pattern, applyDirection);
Result = new PatternTileResult Result = new PatternTileResult
{ {