mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-08 06:45:45 -05:00
Slice 3 of common-line cutting. On costly plate (N26-295: 3/16 RA310S at about $2,500 a sheet) pairing parts one kerf apart fits the part the spacing cannot: the last plate's 38 parts fit on one 48x120 with Auto Nest only with pairs. - Drawing Properties gains "Allow common line" (Drawing.AllowCommonLine, off by default because customer edge-quality needs vary). The nest's CommonLineUsage (Off / When it adds parts / Whenever available) is set in Auto Nest and seeded from Tools > Nest Defaults; both persist (nest.json, defaults.json). - NestPipeline runs the ordinary solve, then, with a level on, trials where eligible parts are offered as kerf-gap pair compounds. Placed pairs expand into parts plus explicit NestJobCommonLine groups, are validated again, and CommonLineRanking keeps a trial only by the level: count first, then sheets; "when it adds parts" keeps the plain layout on a tie, "whenever" also takes a shorter cut or less material, never at the cost of a part. Engines, FillScore and their comparers are unchanged; Off asks the engine exactly what it did. - NestLayoutCheck exempts members only through a sheet's explicit group list and only when the job allows common lines; materializing and committing create Plate.CommonLines. - Server nesting writes common-line jobs as version 3 and replays the recorded trial answers, so the desktop binds the same groups. - Fill Area honors the level through a PlateFillService overload. - The benchmark gains --common-lines <level> --kerf <k>. Benchmark (synthetic nests + N26-295, --parallel 1): with no flag and with --common-lines off every engine matches the base commit; with a level on no layout becomes invalid and three runs save a sheet at equal nested area. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
303 lines
11 KiB
C#
303 lines
11 KiB
C#
using System;
|
|
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 JoinCommonLine_Click(object sender, EventArgs e)
|
|
{
|
|
var editForm = activeForm;
|
|
if (editForm?.PlateView?.Plate == null)
|
|
return;
|
|
var kerf = MachineSettings.CuttingParametersFor(editForm.PlateView.Plate, editForm.Nest).Kerf;
|
|
var result = editForm.PlateView.JoinSelectedCommonLine(kerf);
|
|
if (!result.Succeeded)
|
|
ShowCommonLineNotice(result.Failure);
|
|
}
|
|
|
|
private void RemoveCommonLine_Click(object sender, EventArgs e)
|
|
{
|
|
if (activeForm?.PlateView?.Plate == null)
|
|
return;
|
|
if (!activeForm.PlateView.RemoveSelectedCommonLines())
|
|
ShowCommonLineNotice("None of the selected parts share a common line.");
|
|
}
|
|
|
|
private void CutCommonLinesTwice_Click(object sender, EventArgs e)
|
|
{
|
|
var plateView = activeForm?.PlateView;
|
|
if (plateView?.Plate == null)
|
|
return;
|
|
plateView.SetCutCommonLinesTwice(!plateView.Plate.CutCommonLinesTwice);
|
|
}
|
|
|
|
private void ArrangeMenu_Opening(object sender, EventArgs e)
|
|
{
|
|
var plate = activeForm?.PlateView?.Plate;
|
|
mnuPlateCutCommonLinesTwice.Checked = plate?.CutCommonLinesTwice == true;
|
|
mnuPlateCutCommonLinesTwice.Enabled = plate != null;
|
|
}
|
|
|
|
// Native tests capture this notice without opening a modal dialog in session zero.
|
|
internal System.Action<string> CommonLineNotice { get; set; }
|
|
|
|
private void ShowCommonLineNotice(string message)
|
|
{
|
|
if (CommonLineNotice != null)
|
|
CommonLineNotice(message);
|
|
else
|
|
MessageBox.Show(this, message, "Common Line", 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;
|
|
form.OptionText = "Calculated distance (selection size + part spacing)";
|
|
form.OptionChecked = activeForm.PlateView.CalculatedOffsetDistance;
|
|
|
|
if (form.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
activeForm.PlateView.OffsetIncrementDistance = form.Value;
|
|
activeForm.PlateView.CalculatedOffsetDistance = form.OptionChecked;
|
|
}
|
|
}
|
|
|
|
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)
|
|
MachineSettings.SaveNestDefaults(this, form.GetDefaults());
|
|
}
|
|
}
|
|
|
|
private void SaveCurrentAsDefaults_Click(object sender, EventArgs e)
|
|
{
|
|
if (activeForm == null)
|
|
return;
|
|
|
|
// A plate holds no common-line usage, so the saved defaults keep theirs.
|
|
MachineSettings.SaveNestDefaults(this, NestDefaults.FromPlate(activeForm.Nest.Units, activeForm.PlateView.Plate,
|
|
MachineSettings.LoadSavedNestDefaults(out _)), activeForm.Nest.Machine);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|