mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 01:31:06 -04:00
feat(desktop): draw Auto Nest previews on the trial sheet
NestProgress gains PreviewSheet, a detached plate for the stock the preview parts lie on; Irregular builds one per stock. PlateView draws it (size, edge spacing, clamps) in place of the plate while the preview is shown and drops it whenever the preview is cleared or the plate changes. Auto Nest zooms out when a trial sheet falls outside what it has already shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c997b2e588
commit
d384e63068
9 files changed
+95
-7
No files matched your search
@@ -227,6 +227,8 @@ public class IrregularNestingEngineTests
|
||||
foreach (var preview in previews.Select(r => r.LegacyProgress))
|
||||
{
|
||||
Assert.True(preview.IsOverallBest);
|
||||
Assert.Equal(job.Plates[0].Size, preview.PreviewSheet.Size);
|
||||
Assert.Equal(job.Plates[0].Quadrant, preview.PreviewSheet.Quadrant);
|
||||
Assert.Equal((work.Left, work.Bottom, work.Right, work.Top), (preview.ActiveWorkArea.Left,
|
||||
preview.ActiveWorkArea.Bottom, preview.ActiveWorkArea.Right, preview.ActiveWorkArea.Top));
|
||||
foreach (var part in preview.BestParts)
|
||||
|
||||
@@ -112,6 +112,13 @@ namespace OpenNest.Engine
|
||||
|
||||
public string Description { get; set; }
|
||||
public Box ActiveWorkArea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sheet <see cref="BestParts"/> lie on when it is not the caller's plate (a whole-job
|
||||
/// engine's trial stock); null means the caller's own plate. Detached and never mutated.
|
||||
/// </summary>
|
||||
public Plate PreviewSheet { get; set; }
|
||||
|
||||
public bool IsOverallBest { get; set; }
|
||||
|
||||
public int BestPartCount => BestParts?.Count ?? 0;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.NestingEngines.Irregular;
|
||||
@@ -22,6 +23,7 @@ internal sealed class SheetPreview(
|
||||
internal static readonly TimeSpan Interval = TimeSpan.FromMilliseconds(250);
|
||||
|
||||
private readonly Stopwatch clock = Stopwatch.StartNew();
|
||||
private readonly Dictionary<NestPlateStock, Plate> sheets = new(ReferenceEqualityComparer.Instance);
|
||||
private TimeSpan? last;
|
||||
|
||||
/// <summary>Called after each placement on a sheet trial; reports when the interval has passed.</summary>
|
||||
@@ -42,6 +44,9 @@ internal sealed class SheetPreview(
|
||||
part.UpdateBounds();
|
||||
parts.Add(part);
|
||||
}
|
||||
// One detached sheet per stock, shared by its reports; front ends only read it.
|
||||
if (!sheets.TryGetValue(stock, out var sheet))
|
||||
sheets[stock] = sheet = DrawingJobMapper.CreatePlate(stock);
|
||||
|
||||
progress.Report(new NestJobProgress(NestJobStage.EvaluatingCandidate, stock.Id, plateIndex, 0, 0,
|
||||
new NestProgress
|
||||
@@ -50,6 +55,7 @@ internal sealed class SheetPreview(
|
||||
PlateNumber = plateIndex + 1,
|
||||
BestParts = parts,
|
||||
ActiveWorkArea = stock.WorkArea,
|
||||
PreviewSheet = sheet,
|
||||
IsOverallBest = true,
|
||||
Description = $"Irregular: packing plate {plateIndex + 1} on stock {stock.Id} ({parts.Count} parts)",
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Controls;
|
||||
|
||||
public class PlateViewPreviewSheetTests
|
||||
{
|
||||
[Fact]
|
||||
public void PreviewSheetReplacesThePlateUntilThePreviewEnds() => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = new Plate(48, 96);
|
||||
var trial = new Plate(60, 120);
|
||||
using var view = new PlateView { Plate = plate };
|
||||
var part = new Part(new Drawing("square", Square()), new Vector(1, 1));
|
||||
|
||||
view.SetActiveParts(new List<Part> { part }, trial);
|
||||
Assert.Same(trial, view.SheetPlate);
|
||||
Assert.Same(plate, view.DisplayPlate);
|
||||
|
||||
// A preview without a sheet (interactive fill) is drawn on the plate.
|
||||
view.SetActiveParts(new List<Part> { part });
|
||||
Assert.Same(plate, view.SheetPlate);
|
||||
|
||||
view.SetActiveParts(new List<Part> { part }, trial);
|
||||
view.ClearPreviewParts();
|
||||
Assert.Same(plate, view.SheetPlate);
|
||||
|
||||
view.SetActiveParts(new List<Part> { part }, trial);
|
||||
view.Plate = new Plate(48, 96);
|
||||
Assert.Same(view.Plate, view.SheetPlate);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
|
||||
private static CNC.Program Square()
|
||||
{
|
||||
var program = new CNC.Program();
|
||||
program.MoveTo(0, 0);
|
||||
program.LineTo(0, 10);
|
||||
program.LineTo(10, 10);
|
||||
program.LineTo(10, 0);
|
||||
program.LineTo(0, 0);
|
||||
return program;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace OpenNest.Controls
|
||||
|
||||
public void DrawPlate(Graphics g)
|
||||
{
|
||||
var plate = view.DisplayPlate;
|
||||
var plate = view.SheetPlate;
|
||||
var plateRect = new RectangleF
|
||||
{
|
||||
Width = view.LengthWorldToGui(plate.Size.Length),
|
||||
@@ -178,7 +178,7 @@ namespace OpenNest.Controls
|
||||
|
||||
public void DrawClampZones(Graphics g)
|
||||
{
|
||||
var plate = view.DisplayPlate;
|
||||
var plate = view.SheetPlate;
|
||||
if (plate == null || plate.ClampZones.Count == 0)
|
||||
return;
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace OpenNest.Controls
|
||||
|
||||
public void DrawRemovedClamps(Graphics g)
|
||||
{
|
||||
var plate = view.DisplayPlate;
|
||||
var plate = view.SheetPlate;
|
||||
if (plate?.ClampRail == null || plate.RemovedClamps.Count == 0)
|
||||
return;
|
||||
|
||||
|
||||
@@ -197,6 +197,9 @@ namespace OpenNest.Controls
|
||||
private List<LayoutPart> displayParts;
|
||||
private Func<bool> displayIsCurrent;
|
||||
internal Plate DisplayPlate => displayPlate ?? plate;
|
||||
|
||||
/// <summary>The sheet drawn under everything: a proposal's, a preview's trial sheet, or the plate's.</summary>
|
||||
internal Plate SheetPlate => displayPlate ?? previewManager.Sheet ?? plate;
|
||||
internal List<LayoutPart> DisplayLayoutParts => displayParts ?? parts;
|
||||
internal bool HasProposalDisplay => displayPlate != null;
|
||||
|
||||
@@ -845,7 +848,10 @@ namespace OpenNest.Controls
|
||||
Plate.Parts.Add(part);
|
||||
}
|
||||
|
||||
public void SetActiveParts(List<Part> parts) => previewManager.SetActiveParts(parts);
|
||||
/// <summary>Shows preview parts; <paramref name="sheet"/>, when given, is drawn in place of
|
||||
/// the plate's sheet until the preview is cleared (a whole-job engine's trial stock).</summary>
|
||||
public void SetActiveParts(List<Part> parts, Plate sheet = null) =>
|
||||
previewManager.SetActiveParts(parts, sheet);
|
||||
|
||||
public void ClearPreviewParts() => previewManager.ClearPreviewParts();
|
||||
|
||||
|
||||
@@ -15,13 +15,17 @@ namespace OpenNest.Controls
|
||||
|
||||
public IReadOnlyList<LayoutPart> PreviewParts => activeParts;
|
||||
|
||||
/// <summary>The sheet the preview parts lie on when it is not the view's plate; null otherwise.</summary>
|
||||
public Plate Sheet { get; private set; }
|
||||
|
||||
public Brush PreviewBrush => view.ColorScheme.ActivePreviewPartBrush;
|
||||
|
||||
public Pen PreviewPen => view.ColorScheme.ActivePreviewPartPen;
|
||||
|
||||
public void SetActiveParts(List<Part> parts)
|
||||
public void SetActiveParts(List<Part> parts, Plate sheet = null)
|
||||
{
|
||||
activeParts.Clear();
|
||||
Sheet = sheet;
|
||||
|
||||
if (parts != null)
|
||||
{
|
||||
@@ -35,6 +39,7 @@ namespace OpenNest.Controls
|
||||
public void ClearPreviewParts()
|
||||
{
|
||||
activeParts.Clear();
|
||||
Sheet = null;
|
||||
view.Invalidate();
|
||||
}
|
||||
|
||||
@@ -47,6 +52,7 @@ namespace OpenNest.Controls
|
||||
}
|
||||
|
||||
activeParts.Clear();
|
||||
Sheet = null;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -57,6 +63,7 @@ namespace OpenNest.Controls
|
||||
public void Clear()
|
||||
{
|
||||
activeParts.Clear();
|
||||
Sheet = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Windows.Forms;
|
||||
using OpenNest.Diagnostics;
|
||||
using OpenNest.Engine;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Forms.Commands
|
||||
{
|
||||
@@ -79,13 +80,26 @@ namespace OpenNest.Forms.Commands
|
||||
HoldOpenUntilCompleted = true,
|
||||
};
|
||||
var receivingProgress = true;
|
||||
// Everything shown so far; the view zooms out only when a trial sheet falls outside it.
|
||||
var shown = target.PlateView.Plate.BoundingBox(false);
|
||||
var progress = new Progress<NestProgress>(p =>
|
||||
{
|
||||
if (!receivingProgress || target.IsDisposed || cts.IsCancellationRequested)
|
||||
return;
|
||||
progressForm.UpdateProgress(p);
|
||||
if (p.IsOverallBest)
|
||||
target.PlateView.SetActiveParts(p.BestParts);
|
||||
{
|
||||
target.PlateView.SetActiveParts(p.BestParts, p.PreviewSheet);
|
||||
var sheet = p.PreviewSheet?.BoundingBox(false);
|
||||
if (sheet != null && !shown.Contains(sheet))
|
||||
{
|
||||
var left = System.Math.Min(shown.Left, sheet.Left);
|
||||
var bottom = System.Math.Min(shown.Bottom, sheet.Bottom);
|
||||
shown = new Box(left, bottom, System.Math.Max(shown.Right, sheet.Right) - left,
|
||||
System.Math.Max(shown.Top, sheet.Top) - bottom);
|
||||
target.PlateView.ZoomToArea(shown);
|
||||
}
|
||||
}
|
||||
target.PlateView.ActiveWorkArea = p.ActiveWorkArea;
|
||||
});
|
||||
var jobProgress = JobEngineNest.CreateProgress(EngineName, progress);
|
||||
|
||||
@@ -65,7 +65,10 @@ work-area bounds remains invalid.
|
||||
|
||||
While it runs with a progress sink, Irregular previews the sheet it is packing: at most four
|
||||
times a second it reports the placements so far as `NestProgress.BestParts` with the stock's
|
||||
work area, and desktop Auto Nest draws them on the plate view. The preview follows every trial
|
||||
work area and a detached `PreviewSheet` built from the stock. Desktop Auto Nest draws the parts
|
||||
on that sheet (its size, edge spacing and clamps) in place of the current plate's, zooms out when
|
||||
a trial sheet falls outside what it has shown, and returns to the plate when the run ends. The
|
||||
preview follows every trial
|
||||
(each stock, sheet, strategy variant and tail re-plan), so it shows the search at work, not the
|
||||
layout that will be kept. Reporting only reads finished placements, so layouts are identical
|
||||
with or without a sink.
|
||||
|
||||
Reference in new issue
Block a user