Files
OpenNest/OpenNest/Forms/MainForm.Tools.cs
T
ajandClaude Fable 5.1 101d7b5a50 fix(desktop): refuse fills on an outlined plate before the service
PlateFillService throws for a plate with a sheet outline, but the desktop
swallowed it: Fill (Ctrl+F) caught every exception and showed nothing, and
Fill Area's async void either completed silently or let it escape.

PlateFillService.Refusal(plate) names the reason, and every caller checks
it first: Fill and Fill Area put it in the status bar and add nothing,
Pattern Tile offers a new plate only, the MCP fill/pack tools return it as
an Error result, and the console exits 2 with it unless --autonest is given.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-29 00:23:10 -04:00

268 lines
9.4 KiB
C#

using System;
using System.IO;
using System.Windows.Forms;
using OpenNest.Actions;
using OpenNest.Data;
using OpenNest.Geometry;
namespace OpenNest.Forms
{
public partial class MainForm
{
#region Tools Menu Events
private void MeasureArea_Click(object sender, EventArgs e) => WithActive(f => f.PlateView.SetAction(typeof(ActionSelectArea)));
private void BestFitViewer_Click(object sender, EventArgs e)
{
if (activeForm == null)
return;
var plate = activeForm.PlateView.Plate;
var drawings = activeForm.Nest.Drawings;
if (drawings.Count == 0)
{
MessageBox.Show(
"No drawings available.",
"Best-Fit Viewer",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
return;
}
using (var form = new BestFitViewerForm(drawings, plate, activeForm.Nest.Units))
{
if (form.ShowDialog(this) == DialogResult.OK && form.SelectedResult != null)
{
var parts =
form.SelectedParts
?? form.SelectedResult.BuildSourceParts(form.SelectedDrawing);
activeForm.PlateView.SetAction(typeof(ActionClone), parts);
}
}
}
private void ExpandSpacing_Click(object sender, EventArgs e)
{
var editForm = activeForm;
if (editForm == null)
return;
// The search reads the plate on a worker, so a nest or fill that can still
// add parts must finish first.
if (IsExpandSpacingBusy(editForm))
{
ShowExpandSpacingNotice(
"Finish or cancel the current nesting or fill before Expand Spacing."
);
return;
}
// Complex layouts can search for minutes; run behind a Cancel window.
var expanded = editForm.PlateView.ExpandSelected(work =>
OperationProgressForm.Run(
this,
"Expand Spacing",
"Expanding spacing between the selected parts...",
work
)
);
if (!expanded)
ShowExpandSpacingNotice("Select at least two parts on the plate to expand.");
}
// Native tests capture this notice without opening a modal dialog in session zero.
internal System.Action<string> ExpandSpacingNotice { get; set; }
private void ShowExpandSpacingNotice(string message)
{
if (ExpandSpacingNotice != null)
ExpandSpacingNotice(message);
else
MessageBox.Show(
this,
message,
"Expand Spacing",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
private void PatternTile_Click(object sender, EventArgs e)
{
if (activeForm == null)
return;
if (activeForm.Nest.Drawings.Count == 0)
{
MessageBox.Show(
"No drawings available.",
"Pattern Tile",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
return;
}
using (var form = new PatternTileForm(activeForm.Nest, activeForm.PlateView.Plate))
{
if (form.ShowDialog(this) != DialogResult.OK || form.Result == null)
return;
ApplyPatternTile(activeForm, form.Result);
}
}
internal static void ApplyPatternTile(EditNestForm editForm, PatternTileResult result)
{
using var change = editForm.BeginChange("Pattern Tile");
change.MarkChanged();
if (result.Target == PatternTileTarget.CurrentPlate)
{
editForm.PlateView.Plate.Parts.Clear();
foreach (var part in result.Parts)
editForm.PlateView.Plate.Parts.Add(part);
editForm.PlateView.ZoomToFit();
}
else
{
// The empty new-plate sentinel takes the pattern; a plate created beside it
// would be dropped as a second trailing empty plate before the parts arrive.
var plate = editForm.PlateManager.GetOrCreateEmpty();
plate.Size = result.PlateSize;
foreach (var part in result.Parts)
plate.Parts.Add(part);
editForm.PlateManager.LoadAt(editForm.Nest.Plates.IndexOf(plate));
}
editForm.Nest.UpdateDrawingQuantities();
change.Complete();
}
private void SetOffsetIncrement_Click(object sender, EventArgs e)
{
if (activeForm == null)
return;
using var form = new SetValueForm();
form.Text = "Set Offset Increment";
form.Value = activeForm.PlateView.OffsetIncrementDistance;
if (form.ShowDialog(this) == DialogResult.OK)
activeForm.PlateView.OffsetIncrementDistance = form.Value;
}
private void SetRotationIncrement_Click(object sender, EventArgs e)
{
if (activeForm == null)
return;
using var form = new SetValueForm();
form.Text = "Set Rotation Increment";
form.Value = activeForm.PlateView.RotateIncrementAngle;
form.Maximum = 360;
if (form.ShowDialog(this) == DialogResult.OK)
activeForm.PlateView.RotateIncrementAngle = form.Value;
}
private void Options_Click(object sender, EventArgs e)
{
using var form = new OptionsForm();
form.ShowDialog(this);
RefreshLedgerHolds(); // The ledger address or key may have changed.
}
private void NestDefaults_Click(object sender, EventArgs e)
{
var defaults = MachineSettings.LoadSavedNestDefaults(out _);
SharedSettings.WarnIfOffline(this);
using (var form = new NestDefaultsForm(defaults, MachineSettings.LoadMachines(), SharedSettings.NestDefaultsSource()))
{
if (form.ShowDialog(this) == DialogResult.OK)
SaveNestDefaults(form.GetDefaults());
}
}
private void SaveNestDefaults(NestDefaults defaults)
{
try
{
MachineSettings.SaveNestDefaults(defaults);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
MessageBox.Show(this, "The nest defaults could not be saved.\n\n" + ex.Message, "Nest Defaults",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void SaveCurrentAsDefaults_Click(object sender, EventArgs e)
{
if (activeForm == null)
return;
var defaults = NestDefaults.FromPlate(activeForm.Nest.Units, activeForm.PlateView.Plate);
// The nest's machine becomes the default; a nest without one keeps the saved choice.
defaults.MachineId = activeForm.Nest.Machine?.Id ?? MachineSettings.LoadSavedNestDefaults(out _).MachineId;
SaveNestDefaults(defaults);
}
private void MachineConfig_Click(object sender, EventArgs e)
{
var provider = MachineSettings.CreateMachineProvider(refresh: true);
SharedSettings.WarnIfOffline(this);
using (var form = new MachineConfigForm(provider))
{
var source = SharedSettings.MachineSource(provider);
if (source.Length > 0)
form.Text += $" ({source})";
form.ShowDialog(this);
}
}
private void InitializeAlignMenu()
{
mnuPlateAlignLeft.Tag = AlignType.Left;
mnuPlateAlignRight.Tag = AlignType.Right;
mnuPlateAlignTop.Tag = AlignType.Top;
mnuPlateAlignBottom.Tag = AlignType.Bottom;
mnuPlateAlignHorizontal.Tag = AlignType.Horizontally;
mnuPlateAlignVertically.Tag = AlignType.Vertically;
mnuPlateEvenlySpaceHorizontal.Tag = AlignType.EvenlySpaceHorizontally;
mnuPlateEvenlySpaceVertical.Tag = AlignType.EvenlySpaceVertically;
}
private void Align_Click(object sender, EventArgs e)
{
if (((ToolStripItem)sender).Tag is AlignType type)
WithActive(f => f.PlateView.AlignSelected(type));
}
#endregion Tools Menu Events
private void CenterParts_Click(object sender, EventArgs e)
{
var view = activeForm?.PlateView;
if (view?.Plate == null || view.Parts.Count == 0)
return;
var offset = view.Plate.OffsetToCenterPartsInWorkArea();
using var change = view.BeginChange("Center Parts");
if (offset.X != 0 || offset.Y != 0)
change.MarkChanged();
foreach (var part in view.Parts)
part.Offset(offset);
view.Invalidate();
change.Complete();
}
}
}