feat: use nest template for BOM import spacing defaults, editable per group

BOM import now loads the nest template to populate plate size, part
spacing, edge spacing, and quadrant instead of hard-coding defaults.
Spacing columns are shown per material+thickness group on the Groups
tab so each combo can be adjusted independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 09:10:06 -04:00
parent ea3c6afbdd
commit a8e42fb4b5
2 changed files with 133 additions and 69 deletions

View File

@@ -50,9 +50,9 @@ namespace OpenNest.Forms
((System.ComponentModel.ISupportInitialize)dgvGroups).BeginInit();
pnlBottom.SuspendLayout();
SuspendLayout();
//
//
// grpInput
//
//
grpInput.Controls.Add(tbl);
grpInput.Dock = System.Windows.Forms.DockStyle.Top;
grpInput.Location = new System.Drawing.Point(0, 0);
@@ -62,9 +62,9 @@ namespace OpenNest.Forms
grpInput.TabIndex = 0;
grpInput.TabStop = false;
grpInput.Text = "Input";
//
//
// tbl
//
//
tbl.ColumnCount = 3;
tbl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
tbl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
@@ -92,9 +92,9 @@ namespace OpenNest.Forms
tbl.RowStyles.Add(new System.Windows.Forms.RowStyle());
tbl.Size = new System.Drawing.Size(792, 172);
tbl.TabIndex = 0;
//
//
// lblJobName
//
//
lblJobName.Anchor = System.Windows.Forms.AnchorStyles.Left;
lblJobName.AutoSize = true;
lblJobName.Location = new System.Drawing.Point(6, 13);
@@ -103,9 +103,9 @@ namespace OpenNest.Forms
lblJobName.Size = new System.Drawing.Size(63, 15);
lblJobName.TabIndex = 0;
lblJobName.Text = "Job Name:";
//
//
// txtJobName
//
//
tbl.SetColumnSpan(txtJobName, 2);
txtJobName.Dock = System.Windows.Forms.DockStyle.Fill;
txtJobName.Location = new System.Drawing.Point(79, 9);
@@ -113,9 +113,9 @@ namespace OpenNest.Forms
txtJobName.Name = "txtJobName";
txtJobName.Size = new System.Drawing.Size(707, 23);
txtJobName.TabIndex = 1;
//
//
// lblBomFile
//
//
lblBomFile.Anchor = System.Windows.Forms.AnchorStyles.Left;
lblBomFile.AutoSize = true;
lblBomFile.Location = new System.Drawing.Point(6, 45);
@@ -124,9 +124,9 @@ namespace OpenNest.Forms
lblBomFile.Size = new System.Drawing.Size(58, 15);
lblBomFile.TabIndex = 2;
lblBomFile.Text = "BOM File:";
//
//
// txtBomFile
//
//
txtBomFile.Dock = System.Windows.Forms.DockStyle.Fill;
txtBomFile.Location = new System.Drawing.Point(79, 41);
txtBomFile.Margin = new System.Windows.Forms.Padding(3, 6, 3, 3);
@@ -134,9 +134,9 @@ namespace OpenNest.Forms
txtBomFile.ReadOnly = true;
txtBomFile.Size = new System.Drawing.Size(669, 23);
txtBomFile.TabIndex = 3;
//
//
// btnBrowseBom
//
//
btnBrowseBom.Location = new System.Drawing.Point(751, 40);
btnBrowseBom.Margin = new System.Windows.Forms.Padding(0, 5, 3, 3);
btnBrowseBom.Name = "btnBrowseBom";
@@ -144,9 +144,9 @@ namespace OpenNest.Forms
btnBrowseBom.TabIndex = 4;
btnBrowseBom.Text = "...";
btnBrowseBom.Click += BrowseBom_Click;
//
//
// lblDxfFolder
//
//
lblDxfFolder.Anchor = System.Windows.Forms.AnchorStyles.Left;
lblDxfFolder.AutoSize = true;
lblDxfFolder.Location = new System.Drawing.Point(6, 78);
@@ -155,9 +155,9 @@ namespace OpenNest.Forms
lblDxfFolder.Size = new System.Drawing.Size(67, 15);
lblDxfFolder.TabIndex = 5;
lblDxfFolder.Text = "DXF Folder:";
//
//
// txtDxfFolder
//
//
txtDxfFolder.Dock = System.Windows.Forms.DockStyle.Fill;
txtDxfFolder.Location = new System.Drawing.Point(79, 74);
txtDxfFolder.Margin = new System.Windows.Forms.Padding(3, 6, 3, 3);
@@ -165,9 +165,9 @@ namespace OpenNest.Forms
txtDxfFolder.ReadOnly = true;
txtDxfFolder.Size = new System.Drawing.Size(669, 23);
txtDxfFolder.TabIndex = 6;
//
//
// btnBrowseDxf
//
//
btnBrowseDxf.Location = new System.Drawing.Point(751, 73);
btnBrowseDxf.Margin = new System.Windows.Forms.Padding(0, 5, 3, 3);
btnBrowseDxf.Name = "btnBrowseDxf";
@@ -175,9 +175,9 @@ namespace OpenNest.Forms
btnBrowseDxf.TabIndex = 7;
btnBrowseDxf.Text = "...";
btnBrowseDxf.Click += BrowseDxf_Click;
//
//
// lblPlateSize
//
//
lblPlateSize.Anchor = System.Windows.Forms.AnchorStyles.Left;
lblPlateSize.AutoSize = true;
lblPlateSize.Location = new System.Drawing.Point(6, 112);
@@ -186,9 +186,9 @@ namespace OpenNest.Forms
lblPlateSize.Size = new System.Drawing.Size(59, 15);
lblPlateSize.TabIndex = 8;
lblPlateSize.Text = "Plate Size:";
//
//
// platePanel
//
//
platePanel.AutoSize = true;
platePanel.Controls.Add(txtPlateWidth);
platePanel.Controls.Add(lblPlateX);
@@ -199,17 +199,17 @@ namespace OpenNest.Forms
platePanel.Size = new System.Drawing.Size(156, 29);
platePanel.TabIndex = 9;
platePanel.WrapContents = false;
//
//
// txtPlateWidth
//
//
txtPlateWidth.Location = new System.Drawing.Point(3, 3);
txtPlateWidth.Name = "txtPlateWidth";
txtPlateWidth.Size = new System.Drawing.Size(60, 23);
txtPlateWidth.TabIndex = 0;
txtPlateWidth.Text = "60";
//
//
// lblPlateX
//
//
lblPlateX.Anchor = System.Windows.Forms.AnchorStyles.Left;
lblPlateX.AutoSize = true;
lblPlateX.Location = new System.Drawing.Point(69, 7);
@@ -217,17 +217,17 @@ namespace OpenNest.Forms
lblPlateX.Size = new System.Drawing.Size(18, 15);
lblPlateX.TabIndex = 1;
lblPlateX.Text = " x ";
//
//
// txtPlateLength
//
//
txtPlateLength.Location = new System.Drawing.Point(93, 3);
txtPlateLength.Name = "txtPlateLength";
txtPlateLength.Size = new System.Drawing.Size(60, 23);
txtPlateLength.TabIndex = 2;
txtPlateLength.Text = "120";
//
//
// btnAnalyze
//
//
btnAnalyze.Anchor = System.Windows.Forms.AnchorStyles.Right;
tbl.SetColumnSpan(btnAnalyze, 2);
btnAnalyze.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
@@ -291,9 +291,9 @@ namespace OpenNest.Forms
dgvGroups.RowHeadersVisible = false;
dgvGroups.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
dgvGroups.TabIndex = 0;
//
//
// pnlBottom
//
//
pnlBottom.Controls.Add(lblSummary);
pnlBottom.Controls.Add(btnCreateNests);
pnlBottom.Controls.Add(btnClose);
@@ -303,9 +303,9 @@ namespace OpenNest.Forms
pnlBottom.Padding = new System.Windows.Forms.Padding(10);
pnlBottom.Size = new System.Drawing.Size(804, 50);
pnlBottom.TabIndex = 2;
//
//
// lblSummary
//
//
lblSummary.AutoSize = true;
lblSummary.Dock = System.Windows.Forms.DockStyle.Left;
lblSummary.ForeColor = System.Drawing.Color.Gray;
@@ -314,9 +314,9 @@ namespace OpenNest.Forms
lblSummary.Size = new System.Drawing.Size(0, 15);
lblSummary.TabIndex = 0;
lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
//
// btnCreateNests
//
//
btnCreateNests.Dock = System.Windows.Forms.DockStyle.Right;
btnCreateNests.Enabled = false;
btnCreateNests.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
@@ -327,9 +327,9 @@ namespace OpenNest.Forms
btnCreateNests.TabIndex = 1;
btnCreateNests.Text = "Create Nests";
btnCreateNests.Click += CreateNests_Click;
//
//
// btnClose
//
//
btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel;
btnClose.Dock = System.Windows.Forms.DockStyle.Right;
btnClose.Location = new System.Drawing.Point(714, 10);
@@ -338,9 +338,9 @@ namespace OpenNest.Forms
btnClose.TabIndex = 2;
btnClose.Text = "Close";
btnClose.Click += BtnClose_Click;
//
//
// BomImportForm
//
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
CancelButton = btnClose;

View File

@@ -16,8 +16,9 @@ namespace OpenNest.Forms
public partial class BomImportForm : Form
{
private List<BomPartRow> _parts;
private Dictionary<string, (double Width, double Length)> _plateSizes;
private Dictionary<string, GroupSettings> _groupSettings;
private bool _suppressRegroup;
private Nest.PlateSettings _templateDefaults;
public Form MdiParentForm { get; set; }
@@ -25,7 +26,38 @@ namespace OpenNest.Forms
{
InitializeComponent();
_parts = new List<BomPartRow>();
_plateSizes = new Dictionary<string, (double, double)>();
_groupSettings = new Dictionary<string, GroupSettings>();
_templateDefaults = LoadTemplateDefaults();
ApplyTemplateDefaults();
}
private Nest.PlateSettings LoadTemplateDefaults()
{
var templatePath = Properties.Settings.Default.NestTemplatePath;
if (File.Exists(templatePath))
{
try
{
var nest = new NestReader(templatePath).Read();
return nest.PlateDefaults;
}
catch { }
}
// Fallback defaults matching CreateDefaultNest
return new Nest.PlateSettings
{
Size = new Geometry.Size(100, 100),
Quadrant = 1,
PartSpacing = 1,
EdgeSpacing = new Spacing(1, 1, 1, 1),
};
}
private void ApplyTemplateDefaults()
{
txtPlateWidth.Text = _templateDefaults.Size.Width.ToString("0.####");
txtPlateLength.Text = _templateDefaults.Size.Length.ToString("0.####");
}
#region File Browsing
@@ -154,7 +186,7 @@ namespace OpenNest.Forms
_parts.Add(row);
}
_plateSizes.Clear();
_groupSettings.Clear();
}
#endregion
@@ -244,11 +276,11 @@ namespace OpenNest.Forms
private void RebuildGroups()
{
// Save existing plate sizes before rebuilding
SavePlateSizes();
// Save existing settings before rebuilding
SaveGroupSettings();
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var w) ? w : 60;
var defaultLength = double.TryParse(txtPlateLength.Text, out var l) ? l : 120;
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var w) ? w : _templateDefaults.Size.Width;
var defaultLength = double.TryParse(txtPlateLength.Text, out var l) ? l : _templateDefaults.Size.Length;
var groups = _parts
.Where(p => p.IsEditable
@@ -270,6 +302,11 @@ namespace OpenNest.Forms
table.Columns.Add("Total Qty", typeof(int));
table.Columns.Add("Plate Width", typeof(double));
table.Columns.Add("Plate Length", typeof(double));
table.Columns.Add("Part Spacing", typeof(double));
table.Columns.Add("Edge Left", typeof(double));
table.Columns.Add("Edge Bottom", typeof(double));
table.Columns.Add("Edge Right", typeof(double));
table.Columns.Add("Edge Top", typeof(double));
foreach (var group in groups)
{
@@ -277,23 +314,27 @@ namespace OpenNest.Forms
var thickness = group.Key.Thickness;
var key = GroupKey(material, thickness);
var plateWidth = _plateSizes.TryGetValue(key, out var size) ? size.Width : defaultWidth;
var plateLength = _plateSizes.TryGetValue(key, out _) ? size.Length : defaultLength;
var existing = _groupSettings.TryGetValue(key, out var gs);
table.Rows.Add(
material,
thickness,
group.Count(),
group.Sum(p => p.Qty ?? 0),
plateWidth,
plateLength
existing ? gs.PlateWidth : defaultWidth,
existing ? gs.PlateLength : defaultLength,
existing ? gs.PartSpacing : _templateDefaults.PartSpacing,
existing ? gs.EdgeLeft : _templateDefaults.EdgeSpacing.Left,
existing ? gs.EdgeBottom : _templateDefaults.EdgeSpacing.Bottom,
existing ? gs.EdgeRight : _templateDefaults.EdgeSpacing.Right,
existing ? gs.EdgeTop : _templateDefaults.EdgeSpacing.Top
);
}
dgvGroups.DataSource = table;
// Material, Thickness, Parts, Total Qty are read-only
if (dgvGroups.Columns.Count >= 6)
if (dgvGroups.Columns.Count > 0)
{
dgvGroups.Columns["Material"].ReadOnly = true;
dgvGroups.Columns["Thickness"].ReadOnly = true;
@@ -304,22 +345,28 @@ namespace OpenNest.Forms
btnCreateNests.Enabled = table.Rows.Count > 0;
}
private void SavePlateSizes()
private void SaveGroupSettings()
{
if (dgvGroups.DataSource is not DataTable table)
return;
_plateSizes.Clear();
_groupSettings.Clear();
foreach (DataRow row in table.Rows)
{
var material = row["Material"]?.ToString() ?? "";
var thickness = row["Thickness"] is double t ? t : 0;
var key = GroupKey(material, thickness);
var width = row["Plate Width"] is double pw ? pw : 60;
var length = row["Plate Length"] is double pl ? pl : 120;
_plateSizes[key] = (width, length);
_groupSettings[key] = new GroupSettings
{
PlateWidth = row["Plate Width"] is double pw ? pw : _templateDefaults.Size.Width,
PlateLength = row["Plate Length"] is double pl ? pl : _templateDefaults.Size.Length,
PartSpacing = row["Part Spacing"] is double ps ? ps : _templateDefaults.PartSpacing,
EdgeLeft = row["Edge Left"] is double el ? el : _templateDefaults.EdgeSpacing.Left,
EdgeBottom = row["Edge Bottom"] is double eb ? eb : _templateDefaults.EdgeSpacing.Bottom,
EdgeRight = row["Edge Right"] is double er ? er : _templateDefaults.EdgeSpacing.Right,
EdgeTop = row["Edge Top"] is double et ? et : _templateDefaults.EdgeSpacing.Top,
};
}
}
@@ -356,11 +403,11 @@ namespace OpenNest.Forms
if (_parts == null || _parts.Count == 0)
return;
// Save latest plate size edits
SavePlateSizes();
// Save latest group edits
SaveGroupSettings();
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var dw) ? dw : 60;
var defaultLength = double.TryParse(txtPlateLength.Text, out var dl) ? dl : 120;
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var dw) ? dw : _templateDefaults.Size.Width;
var defaultLength = double.TryParse(txtPlateLength.Text, out var dl) ? dl : _templateDefaults.Size.Length;
var groups = _parts
.Where(p => p.IsEditable
@@ -391,8 +438,14 @@ namespace OpenNest.Forms
var thickness = group.Key.Thickness;
var key = GroupKey(material, thickness);
var plateWidth = _plateSizes.TryGetValue(key, out var size) ? size.Width : defaultWidth;
var plateLength = _plateSizes.TryGetValue(key, out _) ? size.Length : defaultLength;
var hasSettings = _groupSettings.TryGetValue(key, out var gs);
var plateWidth = hasSettings ? gs.PlateWidth : defaultWidth;
var plateLength = hasSettings ? gs.PlateLength : defaultLength;
var partSpacing = hasSettings ? gs.PartSpacing : _templateDefaults.PartSpacing;
var edgeLeft = hasSettings ? gs.EdgeLeft : _templateDefaults.EdgeSpacing.Left;
var edgeBottom = hasSettings ? gs.EdgeBottom : _templateDefaults.EdgeSpacing.Bottom;
var edgeRight = hasSettings ? gs.EdgeRight : _templateDefaults.EdgeSpacing.Right;
var edgeTop = hasSettings ? gs.EdgeTop : _templateDefaults.EdgeSpacing.Top;
var nestName = $"{jobName} - {thickness:0.###} {material}";
var nest = new Nest(nestName);
@@ -401,9 +454,9 @@ namespace OpenNest.Forms
nest.PlateDefaults.Size = new Geometry.Size(plateWidth, plateLength);
nest.Thickness = thickness;
nest.Material = new Material(material);
nest.PlateDefaults.Quadrant = 1;
nest.PlateDefaults.PartSpacing = 1;
nest.PlateDefaults.EdgeSpacing = new Spacing(1, 1, 1, 1);
nest.PlateDefaults.Quadrant = _templateDefaults.Quadrant;
nest.PlateDefaults.PartSpacing = partSpacing;
nest.PlateDefaults.EdgeSpacing = new Spacing(edgeLeft, edgeBottom, edgeRight, edgeTop);
foreach (var part in group)
{
@@ -486,4 +539,15 @@ namespace OpenNest.Forms
public string Status { get; set; }
public bool IsEditable { get; set; }
}
internal class GroupSettings
{
public double PlateWidth { get; set; }
public double PlateLength { get; set; }
public double PartSpacing { get; set; }
public double EdgeLeft { get; set; }
public double EdgeBottom { get; set; }
public double EdgeRight { get; set; }
public double EdgeTop { get; set; }
}
}