feat(ui): allow clearing lead-ins and cut-offs while a cut plan runs

The Clear Lead-ins & Cut-offs button stays enabled during planning.
Confirming cancels the running plan, clears once the cancellation lands
so the plates never change under the planner, then replans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-18 18:45:31 -04:00
1 parent 78ca08efe1
commit bbe0f17a64
3 files changed
+94 -7

No files matched your search

@@ -675,6 +675,58 @@ public class CuttingInspectionPanelTests
panel.RequestClose();
});
[Fact]
public void ClearingWhilePlanningCancelsTheRunThenClearsAndReplans() => RunSta(() =>
{
using var editor = Editor();
editor.Show();
editor.PlateManager.LoadAt(0);
var plate = editor.PlateView.Plate;
editor.OpenCuttingInspection();
var first = editor.CuttingInspection!;
PumpUntil(() => first.Proposal != null && !first.IsPlanning);
Field<Button>(first, "applyButton").PerformClick();
Assert.All(plate.Parts, part => Assert.True(part.HasManualLeadIns));
using var entered = new ManualResetEventSlim();
using var release = new ManualResetEventSlim();
var runs = 0;
var leadsDuringRuns = new List<bool>();
editor.CuttingBeforePlan = _ =>
{
leadsDuringRuns.Add(plate.Parts.Any(part => part.HasManualLeadIns));
if (Interlocked.Increment(ref runs) > 1) return;
entered.Set();
release.Wait(TimeSpan.FromSeconds(30));
};
try
{
editor.OpenCuttingInspection();
var panel = editor.CuttingInspection!;
PumpUntil(() => entered.IsSet);
Assert.True(panel.IsPlanning);
var button = Field<Button>(panel, "clearExistingButton");
Assert.True(button.Enabled);
panel.ConfirmClearExisting = _ => true;
button.PerformClick();
// The clear waits for the running plan to cancel; the plates are untouched until then.
Assert.All(plate.Parts, part => Assert.True(part.HasManualLeadIns));
release.Set();
PumpUntil(() => panel.Proposal != null && !panel.IsPlanning);
Assert.Equal([true, false], leadsDuringRuns);
Assert.All(plate.Parts, part => Assert.False(part.HasManualLeadIns));
Assert.False(Field<Control>(panel, "existingWorkBanner").Visible);
Assert.True(panel.Proposal.CanApply);
panel.RequestClose();
}
finally
{
release.Set();
}
});
[Fact]
public void CutOffSettingsOpensTheAutomaticCutOffDialogAndReplansWithItsChoice() => RunSta(() =>
{
+39 -6
View File
@@ -39,6 +39,7 @@ internal sealed class CuttingInspectionPanel : UserControl
private CuttingInspectionMode? pendingMode;
private bool configuringMode;
private bool pendingReplan;
private bool pendingClear;
internal CuttingInspectionMode Mode { get; private set; }
internal PostVerificationReport ValidationReport { get; private set; }
internal Func<Nest, CancellationToken, Task<PostVerificationReport>> Analyze { get; set; }
@@ -647,7 +648,7 @@ internal sealed class CuttingInspectionPanel : UserControl
inspection.CompletePlanning(work);
if (closeRequested || pendingMode != null)
{
pendingReplan = false;
pendingReplan = pendingClear = false;
FinishTransition();
return;
}
@@ -656,6 +657,15 @@ internal sealed class CuttingInspectionPanel : UserControl
pendingReplan = false;
if (closed) return;
inspection = new CuttingProposalInspection();
if (pendingClear)
{
pendingClear = false; // The cancelled run no longer reads the plates.
if (!ClearAppliedWork())
{
SetPlanning(false);
return;
}
}
StartPlanning();
return;
}
@@ -799,7 +809,8 @@ internal sealed class CuttingInspectionPanel : UserControl
{
settingsButton.Enabled = keepOrderCheckBox.Enabled = planButton.Enabled = !running;
cutOffSettingsButton.Enabled = !running;
clearExistingButton.Enabled = !running;
// Clearing mid-plan cancels the run and replans, so it stays enabled unless closing or switching.
clearExistingButton.Enabled = !running || Mode == CuttingInspectionMode.Plan && !closeRequested && pendingMode == null;
previousButton.Enabled = !running && selectedPlate > 0;
nextButton.Enabled = !running && selectedPlate < plates.Length - 1;
acceptWarningsCheckBox.Enabled = !running;
@@ -1149,9 +1160,13 @@ internal sealed class CuttingInspectionPanel : UserControl
existingWorkBanner.Visible = Mode == CuttingInspectionMode.Plan && summary.HasExistingWork;
}
/// <summary>
/// Asks, then clears the applied lead-ins and cut-offs and replans. While a plan is running the
/// clear waits for that run's cancellation to land, so the live plates never change under it.
/// </summary>
private void ClearExistingWork()
{
if (closed || closeRequested || IsPlanning || Mode != CuttingInspectionMode.Plan)
if (closed || closeRequested || pendingMode != null || pendingClear || Mode != CuttingInspectionMode.Plan)
return;
var summary = AppliedCuttingWork.Summarize(plates, plateNumbers, stagedCutOffs?.Definitions);
if (!summary.HasExistingWork)
@@ -1163,8 +1178,26 @@ internal sealed class CuttingInspectionPanel : UserControl
var confirmed = ConfirmClearExisting?.Invoke(message)
?? MessageBox.Show(editor, message, "Clear Lead-ins and Cut-offs", MessageBoxButtons.YesNo,
MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes;
if (!confirmed || closed || closeRequested || IsPlanning)
if (!confirmed || closed || closeRequested || pendingMode != null || pendingClear)
return;
if (IsPlanning)
{
pendingClear = true;
pendingReplan = true;
ClearResult();
SetPlanning(true);
SetStatus(Tone.Neutral, "Cancelling the plan to clear lead-ins and cut-offs…");
if (!inspection.CloseRequested)
inspection.RequestClose();
return;
}
if (ClearAppliedWork())
StartPlanning();
}
/// <summary>Removes the applied lead-ins and cut-offs from the plates in scope; false when it stopped part-way.</summary>
private bool ClearAppliedWork()
{
ClearResult();
try
{
@@ -1175,7 +1208,7 @@ internal sealed class CuttingInspectionPanel : UserControl
{
RefreshExistingWork();
SetStatus(Tone.Bad, "Clearing stopped part-way. Review the plates before planning or posting.", ex.Message);
return;
return false;
}
finally
{
@@ -1187,7 +1220,7 @@ internal sealed class CuttingInspectionPanel : UserControl
view.Invalidate();
editor.UpdatePlateList();
}
StartPlanning();
return true;
}
internal void RequestClose()
+3 -1
View File
@@ -519,7 +519,9 @@ panel is wired in the editor. Non-editor callers still use the shared analyzer.
plan keeps locked lead-ins and existing cut-offs unchanged. `Clear Lead-ins &
Cut-offs...` asks first, then removes every applied lead-in (locked and hand-placed
ones too), every cut-off and the plates' recorded cutting settings, keeping part
poses, order and quantities, and replans from scratch. There is no undo.
poses, order and quantities, and replans from scratch. There is no undo. The button stays
enabled while a plan runs: confirming cancels that run, then clears once the cancellation
lands (so the plates never change under the planner) and replans.
- `Add scrap cut-offs` runs the automatic cut-off planner (defaults 35 in /
889 mm strips, half-strip minimum, 12 in / 304.8 mm tail) on every plate in scope
*before* capture, so the cutting plan sequences the new lines like any