Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-04-15 12:46:40 -04:00
35 changed files with 5023 additions and 652 deletions
+20
View File
@@ -27,6 +27,7 @@ namespace OpenNest.Controls
public event EventHandler FilterChanged;
public event EventHandler<int> BendLineSelected;
public event EventHandler<int> BendLineRemoved;
public event EventHandler<int> BendLineEdited;
public event EventHandler AddBendLineClicked;
public FilterPanel()
@@ -51,6 +52,18 @@ namespace OpenNest.Controls
bendLinesList.SelectedIndexChanged += (s, e) =>
BendLineSelected?.Invoke(this, bendLinesList.SelectedIndex);
var bendEditLink = new LinkLabel
{
Text = "Edit",
AutoSize = true,
Font = new Font("Segoe UI", 8f)
};
bendEditLink.LinkClicked += (s, e) =>
{
if (bendLinesList.SelectedIndex >= 0)
BendLineEdited?.Invoke(this, bendLinesList.SelectedIndex);
};
var bendDeleteLink = new LinkLabel
{
Text = "Remove",
@@ -63,6 +76,12 @@ namespace OpenNest.Controls
BendLineRemoved?.Invoke(this, bendLinesList.SelectedIndex);
};
bendLinesList.DoubleClick += (s, e) =>
{
if (bendLinesList.SelectedIndex >= 0)
BendLineEdited?.Invoke(this, bendLinesList.SelectedIndex);
};
bendAddLink = new LinkLabel
{
Text = "Add Bend Line",
@@ -80,6 +99,7 @@ namespace OpenNest.Controls
WrapContents = false
};
bendLinksPanel.Controls.Add(bendAddLink);
bendLinksPanel.Controls.Add(bendEditLink);
bendLinksPanel.Controls.Add(bendDeleteLink);
bendLinesPanel.ContentPanel.Controls.Add(bendLinesList);
+2 -9
View File
@@ -209,15 +209,8 @@ namespace OpenNest.Controls
private static Entity CloneEntity(Entity entity, Color color)
{
Entity clone = entity switch
{
Line line => new Line(line.StartPoint, line.EndPoint) { Layer = line.Layer, IsVisible = line.IsVisible },
Arc arc => new Arc(arc.Center, arc.Radius, arc.StartAngle, arc.EndAngle, arc.IsReversed) { Layer = arc.Layer, IsVisible = arc.IsVisible },
Circle circle => new Circle(circle.Center, circle.Radius) { Layer = circle.Layer, IsVisible = circle.IsVisible },
_ => null,
};
if (clone != null)
clone.Color = color;
var clone = entity.Clone();
clone.Color = color;
return clone;
}
+12
View File
@@ -99,5 +99,17 @@ namespace OpenNest.Forms
public double BendAngle => (double)numAngle.Value;
public double? BendRadius => chkRadius.Checked ? (double)numRadius.Value : null;
public void LoadBend(Bend bend)
{
cboDirection.SelectedIndex = bend.Direction == BendDirection.Up ? 1 : 0;
if (bend.Angle.HasValue)
numAngle.Value = (decimal)bend.Angle.Value;
if (bend.Radius.HasValue)
{
chkRadius.Checked = true;
numRadius.Value = (decimal)bend.Radius.Value;
}
}
}
}
+24
View File
@@ -41,6 +41,7 @@ namespace OpenNest.Forms
filterPanel.FilterChanged += OnFilterChanged;
filterPanel.BendLineSelected += OnBendLineSelected;
filterPanel.BendLineRemoved += OnBendLineRemoved;
filterPanel.BendLineEdited += OnBendLineEdited;
filterPanel.AddBendLineClicked += OnAddBendLineClicked;
entityView1.LinePicked += OnLinePicked;
entityView1.PickCancelled += OnPickCancelled;
@@ -292,6 +293,29 @@ namespace OpenNest.Forms
entityView1.Invalidate();
}
private void OnBendLineEdited(object sender, int index)
{
var item = CurrentItem;
if (item == null || index < 0 || index >= item.Bends.Count) return;
var bend = item.Bends[index];
using var dialog = new BendLineDialog();
dialog.LoadBend(bend);
if (dialog.ShowDialog(this) != DialogResult.OK) return;
bend.Direction = dialog.Direction;
bend.Angle = dialog.BendAngle;
bend.Radius = dialog.BendRadius;
Bend.UpdateEtchEntities(item.Entities, item.Bends);
entityView1.Entities.Clear();
entityView1.Entities.AddRange(item.Entities);
entityView1.Bends = item.Bends;
filterPanel.LoadItem(item.Entities, item.Bends);
entityView1.Invalidate();
}
private void OnQuantityChanged(object sender, EventArgs e)
{
var item = CurrentItem;
+6 -1
View File
@@ -7,6 +7,7 @@ using OpenNest.Engine.Sequencing;
using OpenNest.IO;
using OpenNest.Math;
using OpenNest.Properties;
using OpenNest.Shapes;
using System;
using System.ComponentModel;
using System.Diagnostics;
@@ -453,7 +454,11 @@ namespace OpenNest.Forms
public void ResizePlateToFitParts()
{
PlateView.Plate.AutoSize(Settings.Default.AutoSizePlateFactor);
var options = new PlateSizeOptions
{
SnapIncrement = Settings.Default.AutoSizePlateFactor,
};
PlateView.Plate.SnapToStandardSize(options);
PlateView.ZoomToPlate();
PlateView.Refresh();
UpdatePlateList();
+145 -17
View File
@@ -180,27 +180,66 @@ namespace OpenNest.Forms
y += 18;
var tb = new TextBox
Control editor;
if (prop.PropertyType == typeof(bool))
{
Location = new Point(parametersPanel.Padding.Left, y),
Width = panelWidth,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
var cb = new CheckBox
{
Location = new Point(parametersPanel.Padding.Left, y),
AutoSize = true,
Checked = sourceValues != null && (bool)prop.GetValue(sourceValues)
};
cb.CheckedChanged += (s, ev) => UpdatePreview();
editor = cb;
}
else if (prop.PropertyType == typeof(string) && prop.Name == "PipeSize")
{
var combo = new ComboBox
{
Location = new Point(parametersPanel.Padding.Left, y),
Width = panelWidth,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
DropDownStyle = ComboBoxStyle.DropDownList
};
if (sourceValues != null)
// Initial population: every entry; the filter runs on first UpdatePreview.
foreach (var entry in PipeSizes.All)
combo.Items.Add(entry.Label);
var initial = sourceValues != null ? (string)prop.GetValue(sourceValues) : null;
if (!string.IsNullOrEmpty(initial) && combo.Items.Contains(initial))
combo.SelectedItem = initial;
else if (combo.Items.Count > 0)
combo.SelectedIndex = 0;
combo.SelectedIndexChanged += (s, ev) => UpdatePreview();
editor = combo;
}
else
{
if (prop.PropertyType == typeof(int))
tb.Text = ((int)prop.GetValue(sourceValues)).ToString();
else
tb.Text = ((double)prop.GetValue(sourceValues)).ToString("G");
var tb = new TextBox
{
Location = new Point(parametersPanel.Padding.Left, y),
Width = panelWidth,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
if (sourceValues != null)
{
if (prop.PropertyType == typeof(int))
tb.Text = ((int)prop.GetValue(sourceValues)).ToString();
else
tb.Text = ((double)prop.GetValue(sourceValues)).ToString("G");
}
tb.TextChanged += (s, ev) => UpdatePreview();
editor = tb;
}
tb.TextChanged += (s, ev) => UpdatePreview();
parameterBindings.Add(new ParameterBinding { Property = prop, Control = tb });
parameterBindings.Add(new ParameterBinding { Property = prop, Control = editor });
parametersPanel.Controls.Add(label);
parametersPanel.Controls.Add(tb);
parametersPanel.Controls.Add(editor);
y += 30;
}
@@ -212,6 +251,8 @@ namespace OpenNest.Forms
{
if (suppressPreview || selectedEntry == null) return;
UpdatePipeSizeFilter();
try
{
var shape = CreateShapeFromInputs();
@@ -223,9 +264,17 @@ namespace OpenNest.Forms
if (drawing?.Program != null)
{
var bb = drawing.Program.BoundingBox();
previewBox.SetInfo(
nameTextBox.Text,
string.Format("{0:F3} x {1:F3}", bb.Size.Length, bb.Size.Width));
var info = string.Format("{0:F3} x {1:F3}", bb.Size.Length, bb.Size.Width);
if (shape is PipeFlangeShape flange
&& !flange.Blind
&& !string.IsNullOrEmpty(flange.PipeSize)
&& !PipeSizes.TryGetOD(flange.PipeSize, out _))
{
info += " — Invalid pipe size, no bore cut";
}
previewBox.SetInfo(nameTextBox.Text, info);
}
}
catch
@@ -234,6 +283,72 @@ namespace OpenNest.Forms
}
}
private void UpdatePipeSizeFilter()
{
// Find the PipeSize combo and the numeric inputs it depends on.
ComboBox pipeCombo = null;
double holePattern = 0, holeDia = 0, clearance = 0;
bool blind = false;
foreach (var binding in parameterBindings)
{
var name = binding.Property.Name;
if (name == "PipeSize" && binding.Control is ComboBox cb)
pipeCombo = cb;
else if (name == "HolePatternDiameter" && binding.Control is TextBox tb1)
double.TryParse(tb1.Text, out holePattern);
else if (name == "HoleDiameter" && binding.Control is TextBox tb2)
double.TryParse(tb2.Text, out holeDia);
else if (name == "PipeClearance" && binding.Control is TextBox tb3)
double.TryParse(tb3.Text, out clearance);
else if (name == "Blind" && binding.Control is CheckBox chk)
blind = chk.Checked;
}
if (pipeCombo == null)
return;
// Disable when blind, but keep visible with the selection preserved.
pipeCombo.Enabled = !blind;
// Compute filter: pipeOD + clearance < HolePatternDiameter - HoleDiameter.
var maxPipeOD = holePattern - holeDia - clearance;
var fittingLabels = PipeSizes.GetFittingSizes(maxPipeOD).Select(e => e.Label).ToList();
// Sequence-equal on existing items — no-op if unchanged (avoids flicker).
var currentLabels = pipeCombo.Items.Cast<string>().ToList();
if (currentLabels.SequenceEqual(fittingLabels))
return;
var previousSelection = pipeCombo.SelectedItem as string;
pipeCombo.BeginUpdate();
try
{
pipeCombo.Items.Clear();
foreach (var label in fittingLabels)
pipeCombo.Items.Add(label);
if (fittingLabels.Count == 0)
{
// No pipe fits — leave unselected.
}
else if (previousSelection != null && fittingLabels.Contains(previousSelection))
{
pipeCombo.SelectedItem = previousSelection;
}
else
{
// Select the largest (last, since PipeSizes.All is sorted ascending).
pipeCombo.SelectedIndex = fittingLabels.Count - 1;
}
}
finally
{
pipeCombo.EndUpdate();
}
}
private ShapeDefinition CreateShapeFromInputs()
{
var shape = (ShapeDefinition)Activator.CreateInstance(selectedEntry.ShapeType);
@@ -241,6 +356,19 @@ namespace OpenNest.Forms
foreach (var binding in parameterBindings)
{
if (binding.Property.PropertyType == typeof(bool))
{
var cb = (CheckBox)binding.Control;
binding.Property.SetValue(shape, cb.Checked);
continue;
}
if (binding.Control is ComboBox combo)
{
binding.Property.SetValue(shape, combo.SelectedItem?.ToString());
continue;
}
var tb = (TextBox)binding.Control;
if (binding.Property.PropertyType == typeof(int))