mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 15:45:44 -05:00
feat(nudge): step the selection by a calculated distance
Set Offset Increment gains a "Calculated distance" option. With it on, the arrow keys move the selection by its own size plus the part spacing, measured on its geometry like a fill's copy distance, so it lands tight without a push back. The move stops early at an edge, clamp or part, and a clone preview steps clear of the copy just placed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5bc03bea1
commit
d98bee87ec
13 files changed
+398
-16
No files matched your search
@@ -0,0 +1,110 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenNest.Engine.Tests.Fill;
|
||||
|
||||
/// <summary>
|
||||
/// Compactor.Step moves a selection by its own repeat distance at the part spacing,
|
||||
/// and like a push never passes the work-area edge or another part.
|
||||
/// </summary>
|
||||
public class CompactorStepTests
|
||||
{
|
||||
// Plate: 24 x 24, 0.5 edge spacing, part spacing 1.0; work area 0.5..23.5.
|
||||
private static Plate MakePlate()
|
||||
{
|
||||
var plate = new Plate(new Size(24, 24));
|
||||
plate.EdgeSpacing = new Spacing(0.5, 0.5);
|
||||
plate.PartSpacing = 1.0;
|
||||
return plate;
|
||||
}
|
||||
|
||||
private static Program Polygon(params (double X, double Y)[] points)
|
||||
{
|
||||
var program = new Program();
|
||||
program.MoveTo(points[0].X, points[0].Y);
|
||||
for (var i = 1; i < points.Length; i++)
|
||||
program.LineTo(points[i].X, points[i].Y);
|
||||
program.LineTo(points[0].X, points[0].Y);
|
||||
return program;
|
||||
}
|
||||
|
||||
private static Part Add(Plate plate, Program program, double x, double y)
|
||||
{
|
||||
var part = new Part(new Drawing($"p{plate.Parts.Count}", program), new Vector(x, y));
|
||||
plate.Parts.Add(part);
|
||||
return part;
|
||||
}
|
||||
|
||||
private static Part AddSquare(Plate plate, double x, double y) =>
|
||||
Add(plate, Polygon((0, 0), (3, 0), (3, 3), (0, 3)), x, y);
|
||||
|
||||
[Theory]
|
||||
[InlineData(PushDirection.Right, 4, 0)]
|
||||
[InlineData(PushDirection.Left, -4, 0)]
|
||||
[InlineData(PushDirection.Up, 0, 4)]
|
||||
[InlineData(PushDirection.Down, 0, -4)]
|
||||
public void SquareStepsItsSizePlusTheSpacing(PushDirection direction, double dx, double dy)
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var part = AddSquare(plate, 10, 10);
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { part }, plate, direction);
|
||||
|
||||
Assert.Equal(4, moved, 3);
|
||||
Assert.Equal(10 + dx, part.Location.X, 3);
|
||||
Assert.Equal(10 + dy, part.Location.Y, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShapeThatNestsIntoItsCopyStepsLessThanItsSize()
|
||||
{
|
||||
// Parallelogram 6 wide whose slanted sides run along (2, 3): a copy beside it
|
||||
// needs only sqrt(13)/3 beyond the 4-unit base for a 1-unit gap between them.
|
||||
var plate = MakePlate();
|
||||
var part = Add(plate, Polygon((0, 0), (4, 0), (6, 3), (2, 3)), 2, 10);
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { part }, plate, PushDirection.Right);
|
||||
|
||||
Assert.Equal(4 + System.Math.Sqrt(13) / 3, moved, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GroupStepsAsOnePatternKeepingItsOwnSpacing()
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var a = AddSquare(plate, 2, 10);
|
||||
var b = AddSquare(plate, 6, 10);
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { a, b }, plate, PushDirection.Right);
|
||||
|
||||
Assert.Equal(8, moved, 3);
|
||||
Assert.Equal(10, a.Location.X, 3);
|
||||
Assert.Equal(14, b.Location.X, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StopsAtAnotherPartInsteadOfPassingIt()
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var part = AddSquare(plate, 2, 10);
|
||||
AddSquare(plate, 7, 10); // 2 clear of the mover: it may close 1 at 1.0 spacing.
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { part }, plate, PushDirection.Right);
|
||||
|
||||
Assert.Equal(1, moved, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StopsAtTheWorkAreaEdge()
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var part = AddSquare(plate, 18, 10); // Right side at 21, 2.5 from the work area's.
|
||||
|
||||
var moved = Compactor.Step(new List<Part> { part }, plate, PushDirection.Right);
|
||||
|
||||
Assert.Equal(2.5, moved, 3);
|
||||
}
|
||||
}
|
||||
@@ -31,16 +31,46 @@ namespace OpenNest.Engine.Fill
|
||||
public static double Push(List<Part> movingParts, Plate plate, double angle) =>
|
||||
Push(movingParts, plate, new Vector(System.Math.Cos(angle), System.Math.Sin(angle)));
|
||||
|
||||
private static double Push(List<Part> movingParts, Plate plate, Vector direction)
|
||||
private static double Push(List<Part> movingParts, Plate plate, Vector direction) =>
|
||||
Push(movingParts, plate, direction, double.MaxValue);
|
||||
|
||||
private static double Push(List<Part> movingParts, Plate plate, Vector direction, double limit)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p) && !IntersectsAny(p, movingParts))
|
||||
.ToList();
|
||||
|
||||
return Push(movingParts, plate.WorkArea(), direction,
|
||||
return Push(movingParts, plate.WorkArea(), direction, limit,
|
||||
new ObstacleSet(obstacleParts, plate.PartSpacing), ZoneObstacles(plate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves movingParts one repeat step along <paramref name="direction"/>: the
|
||||
/// distance at which a copy left behind would sit at the plate's part spacing,
|
||||
/// measured on the parts' geometry like a fill's copy distance, so shapes that
|
||||
/// nest into their own copy step less than their size. Like
|
||||
/// <see cref="Push(List{Part}, Plate, PushDirection)"/>, the move stops early at
|
||||
/// the work-area edge, a clamp zone or another part, never passing one.
|
||||
/// Returns the distance moved.
|
||||
/// </summary>
|
||||
public static double Step(List<Part> movingParts, Plate plate, PushDirection direction)
|
||||
{
|
||||
if (movingParts.Count == 0)
|
||||
return 0;
|
||||
|
||||
var pattern = new Pattern();
|
||||
pattern.Parts.AddRange(movingParts);
|
||||
pattern.UpdateBounds();
|
||||
var axis = SpatialQuery.IsHorizontalDirection(direction)
|
||||
? NestDirection.Horizontal
|
||||
: NestDirection.Vertical;
|
||||
var step = new FillLinear(plate.WorkArea(), plate.PartSpacing).CopyDistance(pattern, axis);
|
||||
if (!double.IsFinite(step) || step <= 0)
|
||||
return 0;
|
||||
|
||||
return Push(movingParts, plate, SpatialQuery.DirectionToOffset(direction, 1.0), step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
||||
/// </summary>
|
||||
@@ -52,19 +82,27 @@ namespace OpenNest.Engine.Fill
|
||||
Vector direction
|
||||
) => Push(movingParts, workArea, direction, new ObstacleSet(obstacleParts, partSpacing));
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts to the first contact with the work-area edge or an obstacle,
|
||||
/// keeping each obstacle set's own spacing.
|
||||
/// </summary>
|
||||
private static double Push(
|
||||
List<Part> movingParts,
|
||||
Box workArea,
|
||||
Vector direction,
|
||||
params ObstacleSet[] obstacleSets
|
||||
) => Push(movingParts, workArea, direction, double.MaxValue, obstacleSets);
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts at most <paramref name="limit"/> toward the first contact
|
||||
/// with the work-area edge or an obstacle, keeping each obstacle set's own spacing.
|
||||
/// </summary>
|
||||
private static double Push(
|
||||
List<Part> movingParts,
|
||||
Box workArea,
|
||||
Vector direction,
|
||||
double limit,
|
||||
params ObstacleSet[] obstacleSets
|
||||
)
|
||||
{
|
||||
var opposite = -direction;
|
||||
var distance = double.MaxValue;
|
||||
var distance = limit;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
|
||||
@@ -196,6 +196,14 @@ namespace OpenNest.Engine.Fill
|
||||
return maxCopyDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Geometry-aware distance between a pattern and its next copy along an axis at
|
||||
/// this filler's part spacing. A copy is a translation, so the distance is the
|
||||
/// same in the opposite direction.
|
||||
/// </summary>
|
||||
public double CopyDistance(Pattern pattern, NestDirection direction) =>
|
||||
FindPatternCopyDistance(pattern, direction, new OffsetPerimeterCache(HalfSpacing));
|
||||
|
||||
/// <summary>
|
||||
/// Tiles a pattern along the given axis, returning the cloned parts
|
||||
/// (does not include the original pattern's parts). For multi-part
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Actions;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Geometry;
|
||||
using static OpenNest.WinForms.Tests.FormTesting;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// The arrow keys move the selection by the fixed offset increment, or, with the calculated
|
||||
/// distance on, by the selection's own size plus the part spacing, so no push back is needed.
|
||||
/// </summary>
|
||||
public class PlateViewStepTests
|
||||
{
|
||||
private static Drawing Square(string name)
|
||||
{
|
||||
var program = new CNC.Program();
|
||||
program.Codes.Add(new CNC.RapidMove(new Vector(0, 0)));
|
||||
program.Codes.Add(new CNC.LinearMove(new Vector(4, 0)));
|
||||
program.Codes.Add(new CNC.LinearMove(new Vector(4, 4)));
|
||||
program.Codes.Add(new CNC.LinearMove(new Vector(0, 4)));
|
||||
program.Codes.Add(new CNC.LinearMove(new Vector(0, 0)));
|
||||
return new Drawing(name, program);
|
||||
}
|
||||
|
||||
private static Plate MakePlate() => new(48, 96) { PartSpacing = 1 };
|
||||
|
||||
private static void Arrow(PlateView view, Keys key) =>
|
||||
typeof(PlateView).GetMethod("ProcessDialogKey", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.Invoke(view, new object[] { key });
|
||||
|
||||
[Fact]
|
||||
public void ArrowKeysKeepTheOffsetIncrementByDefault() => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var part = new Part(Square("part"), new Vector(10, 10));
|
||||
plate.Parts.Add(part);
|
||||
using var view = new PlateView { Plate = plate, Size = new System.Drawing.Size(600, 600) };
|
||||
view.SelectAll();
|
||||
|
||||
Arrow(view, Keys.Right);
|
||||
|
||||
Assert.Equal(10 + view.OffsetIncrementDistance, part.Location.X, 6);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
|
||||
[Theory]
|
||||
[InlineData(Keys.Right, 15, 10)]
|
||||
[InlineData(Keys.Left, 5, 10)]
|
||||
[InlineData(Keys.Up, 10, 15)]
|
||||
[InlineData(Keys.Down, 10, 5)]
|
||||
public void CalculatedDistanceStepsTheSizePlusTheSpacing(Keys key, double x, double y) => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = MakePlate();
|
||||
var part = new Part(Square("part"), new Vector(10, 10));
|
||||
plate.Parts.Add(part);
|
||||
using var view = new PlateView { Plate = plate, Size = new System.Drawing.Size(600, 600) };
|
||||
view.CalculatedOffsetDistance = true;
|
||||
view.SelectAll();
|
||||
|
||||
Arrow(view, key);
|
||||
|
||||
Assert.Equal(x, part.Location.X, 3);
|
||||
Assert.Equal(y, part.Location.Y, 3);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
|
||||
[Fact]
|
||||
public void CalculatedDistanceStepsAClonePreviewOffTheCopyJustPlaced() => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = MakePlate();
|
||||
using var view = new PlateView { Plate = plate, Size = new System.Drawing.Size(600, 600) };
|
||||
view.ZoomToFit();
|
||||
view.CalculatedOffsetDistance = true;
|
||||
view.SetAction(typeof(ActionClone), Square("new"));
|
||||
Raise(view, "OnMouseMove", new MouseEventArgs(MouseButtons.None, 0, 300, 300, 0));
|
||||
|
||||
Raise(view, "OnKeyDown", new KeyEventArgs(Keys.F1));
|
||||
Arrow(view, Keys.Right);
|
||||
Raise(view, "OnKeyDown", new KeyEventArgs(Keys.F1));
|
||||
|
||||
Assert.Equal(2, plate.Parts.Count);
|
||||
Assert.Equal(5, plate.Parts[1].Location.X - plate.Parts[0].Location.X, 3);
|
||||
Assert.Equal(plate.Parts[0].Location.Y, plate.Parts[1].Location.Y, 6);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
using static OpenNest.WinForms.Tests.FormTesting;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
public class SetValueFormTests
|
||||
{
|
||||
[Fact]
|
||||
public void OptionShowsACheckBoxThatDisablesTheValue() => StaTestThread.Run(() =>
|
||||
{
|
||||
using var form = new SetValueForm { ShowInTaskbar = false };
|
||||
var bare = form.ClientSize;
|
||||
var option = Assert.Single(All<CheckBox>(form));
|
||||
var value = Assert.Single(All<OpenNest.Controls.NumericUpDown>(form));
|
||||
|
||||
form.OptionText = "Calculated distance (selection size + part spacing)";
|
||||
form.OptionText = "Calculated distance (selection size + part spacing)"; // Grows once.
|
||||
form.OptionChecked = true;
|
||||
form.Show();
|
||||
|
||||
Assert.True(option.Visible);
|
||||
Assert.True(form.ClientSize.Height > bare.Height);
|
||||
Assert.True(option.Bottom <= form.ClientSize.Height);
|
||||
Assert.True(option.Right <= form.ClientSize.Width);
|
||||
Assert.False(value.Enabled);
|
||||
|
||||
form.OptionChecked = false;
|
||||
Assert.True(value.Enabled);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
|
||||
[Fact]
|
||||
public void WithoutAnOptionTheDialogKeepsItsSize() => StaTestThread.Run(() =>
|
||||
{
|
||||
using var form = new SetValueForm();
|
||||
var bare = form.ClientSize;
|
||||
|
||||
form.OptionText = "shown";
|
||||
form.OptionText = "";
|
||||
|
||||
Assert.Equal(bare, form.ClientSize);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
}
|
||||
@@ -288,6 +288,12 @@ namespace OpenNest.Controls
|
||||
|
||||
public double OffsetIncrementDistance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, the arrow keys step the selection by its own size plus the part
|
||||
/// spacing instead of <see cref="OffsetIncrementDistance"/>.
|
||||
/// </summary>
|
||||
public bool CalculatedOffsetDistance { get; set; }
|
||||
|
||||
public Plate Plate
|
||||
{
|
||||
get { return plate; }
|
||||
@@ -720,7 +726,7 @@ namespace OpenNest.Controls
|
||||
break;
|
||||
|
||||
case Keys.Left:
|
||||
selection.NudgeSelected(-OffsetIncrementDistance, 0);
|
||||
NudgeSelected(PushDirection.Left);
|
||||
break;
|
||||
|
||||
case Keys.X:
|
||||
@@ -744,21 +750,34 @@ namespace OpenNest.Controls
|
||||
break;
|
||||
|
||||
case Keys.Right:
|
||||
selection.NudgeSelected(OffsetIncrementDistance, 0);
|
||||
NudgeSelected(PushDirection.Right);
|
||||
break;
|
||||
|
||||
case Keys.Up:
|
||||
selection.NudgeSelected(0, OffsetIncrementDistance);
|
||||
NudgeSelected(PushDirection.Up);
|
||||
break;
|
||||
|
||||
case Keys.Down:
|
||||
selection.NudgeSelected(0, -OffsetIncrementDistance);
|
||||
NudgeSelected(PushDirection.Down);
|
||||
break;
|
||||
}
|
||||
|
||||
return base.ProcessDialogKey(keyData);
|
||||
}
|
||||
|
||||
/// <summary>An arrow key: the fixed offset increment, or the calculated step.</summary>
|
||||
public void NudgeSelected(PushDirection direction)
|
||||
{
|
||||
if (CalculatedOffsetDistance)
|
||||
{
|
||||
selection.StepSelected(direction);
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = SpatialQuery.DirectionToOffset(direction, OffsetIncrementDistance);
|
||||
selection.NudgeSelected(offset.X, offset.Y);
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
if (displayPlate != null && displayIsCurrent?.Invoke() != true)
|
||||
|
||||
@@ -107,6 +107,26 @@ namespace OpenNest.Controls
|
||||
change.Complete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the selection one repeat step in <paramref name="direction"/>: its own
|
||||
/// size along that axis plus the part spacing, measured on its geometry
|
||||
/// (<see cref="Compactor.Step"/>). It stops early at an edge or another part.
|
||||
/// Repeated steps undo as one move, like nudges.
|
||||
/// </summary>
|
||||
public void StepSelected(PushDirection direction)
|
||||
{
|
||||
if (selectedParts.Count == 0)
|
||||
return;
|
||||
|
||||
using var change = view.BeginChange("Nudge", new SelectionKey("Nudge", view.Plate, selectedParts));
|
||||
var movingParts = selectedParts.Select(p => p.BasePart).ToList();
|
||||
if (Compactor.Step(movingParts, view.Plate, direction) > 0)
|
||||
change.MarkChanged();
|
||||
selectedParts.ForEach(p => p.IsDirty = true);
|
||||
view.Invalidate();
|
||||
change.Complete();
|
||||
}
|
||||
|
||||
public void AlignSelected(AlignType alignType)
|
||||
{
|
||||
if (selectedParts.Count == 0)
|
||||
|
||||
@@ -152,9 +152,14 @@ namespace OpenNest.Forms
|
||||
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)
|
||||
|
||||
Generated
+18
-2
@@ -32,6 +32,7 @@
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.labelIncrement = new System.Windows.Forms.Label();
|
||||
this.numericUpDownValue = new OpenNest.Controls.NumericUpDown();
|
||||
this.checkBoxOption = new System.Windows.Forms.CheckBox();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numericUpDownValue)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
@@ -56,11 +57,13 @@
|
||||
this.tableLayoutPanel1.Controls.Add(this.buttonSet, 2, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.labelIncrement, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.numericUpDownValue, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.checkBoxOption, 0, 1);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 5);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
this.tableLayoutPanel1.RowCount = 1;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel1.RowCount = 2;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 41F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(344, 41);
|
||||
this.tableLayoutPanel1.TabIndex = 0;
|
||||
//
|
||||
@@ -90,6 +93,18 @@
|
||||
this.numericUpDownValue.Size = new System.Drawing.Size(171, 22);
|
||||
this.numericUpDownValue.TabIndex = 1;
|
||||
//
|
||||
// checkBoxOption
|
||||
//
|
||||
this.checkBoxOption.AutoSize = true;
|
||||
this.tableLayoutPanel1.SetColumnSpan(this.checkBoxOption, 3);
|
||||
this.checkBoxOption.Location = new System.Drawing.Point(3, 44);
|
||||
this.checkBoxOption.Name = "checkBoxOption";
|
||||
this.checkBoxOption.Size = new System.Drawing.Size(15, 14);
|
||||
this.checkBoxOption.TabIndex = 3;
|
||||
this.checkBoxOption.UseVisualStyleBackColor = true;
|
||||
this.checkBoxOption.Visible = false;
|
||||
this.checkBoxOption.CheckedChanged += new System.EventHandler(this.checkBoxOption_CheckedChanged);
|
||||
//
|
||||
// SetValueForm
|
||||
//
|
||||
this.AcceptButton = this.buttonSet;
|
||||
@@ -118,5 +133,6 @@
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Label labelIncrement;
|
||||
private Controls.NumericUpDown numericUpDownValue;
|
||||
private System.Windows.Forms.CheckBox checkBoxOption;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
public partial class SetValueForm : Form
|
||||
{
|
||||
private int optionRowHeight;
|
||||
|
||||
public SetValueForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -44,5 +47,39 @@ namespace OpenNest.Forms
|
||||
get { return (double)numericUpDownValue.Value; }
|
||||
set { numericUpDownValue.Value = (decimal)value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a check box under the value with this text; checking it disables the
|
||||
/// value. Hidden while empty.
|
||||
/// </summary>
|
||||
public string OptionText
|
||||
{
|
||||
get { return checkBoxOption.Text; }
|
||||
set
|
||||
{
|
||||
var show = !string.IsNullOrEmpty(value);
|
||||
checkBoxOption.Text = value;
|
||||
checkBoxOption.Visible = show;
|
||||
// Visible reads false until the form is shown, so track the extra row here.
|
||||
var rowHeight = show
|
||||
? checkBoxOption.PreferredSize.Height + checkBoxOption.Margin.Vertical
|
||||
: 0;
|
||||
var width = show
|
||||
? System.Math.Max(ClientSize.Width,
|
||||
checkBoxOption.PreferredSize.Width + checkBoxOption.Margin.Horizontal + Padding.Horizontal)
|
||||
: ClientSize.Width;
|
||||
ClientSize = new Size(width, ClientSize.Height - optionRowHeight + rowHeight);
|
||||
optionRowHeight = rowHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OptionChecked
|
||||
{
|
||||
get { return checkBoxOption.Checked; }
|
||||
set { checkBoxOption.Checked = value; }
|
||||
}
|
||||
|
||||
private void checkBoxOption_CheckedChanged(object sender, System.EventArgs e) =>
|
||||
numericUpDownValue.Enabled = !checkBoxOption.Checked;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ Default compares the built-in Irregular, Rectangles and Circles engines and keep
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
`Ctrl+F` fill area · `F` zoom to fit · `Shift+wheel` / middle-click rotate · `X`/`Y` push · arrows nudge · `Shift+arrow` push.
|
||||
`Ctrl+F` fill area · `F` zoom to fit · `Shift+wheel` / middle-click rotate · `X`/`Y` push · arrows nudge by the offset increment (`F5`), or with its calculated distance by the selection's own size plus part spacing, landing tight with no push back · `Shift+arrow` push.
|
||||
|
||||
## Status & License
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ Open/incomplete chains and ambiguous contacts conservatively block. Loops are re
|
||||
## Callers
|
||||
|
||||
- PlateView uses `SelectionManager.PushSelected` → `Compactor.Push`. The zero-spacing nudge-and-discard workaround is removed. Cutout contours on stationary obstacles are retained, and the plate entry's existing-overlap filter accounts for holes rather than relying solely on `Part.Intersects` (which compares outer perimeters).
|
||||
- With **Calculated distance** checked in Tools > Set Offset Increment, PlateView's arrow keys use `SelectionManager.StepSelected` → `Compactor.Step`. The step is the selection's linear-fill copy distance (`FillLinear.CopyDistance`) at the plate's part spacing, so a shape that nests into its own copy steps less than its size. That copy distance is the same in both directions along an axis. The move is then a push capped at that step, so it stops at the work-area edge, a clamp zone or another part and never passes one. A clone preview resting on the copy just placed ignores that copy, as a push does with overlapping parts, and so steps clear of it.
|
||||
- Linear fill inherits the shared native-entity query unchanged. Extents fill passes complete boundary loops instead of direction-filtered fragments.
|
||||
- CPU best-fit batches prepare contact topology once and use all vertices plus curve/line interior and curve/curve tangency events. The old leading-half vertex filter cannot establish the next blocker after a skipped touch.
|
||||
- GPU kernels retain nearest-hit reduction and return unsnapped contact witnesses. The shared CPU classifier accepts a blocking witness or replays the full query after a nonblocking witness, preserving tied/later blockers. Both batch APIs honor active buffer lengths and refresh mutated/reused segment arrays. The GPU distance adapter sends only exact cardinal directions to the axis-only slide interface; arbitrary directions and native curves use the shared CPU path.
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ change.Complete();
|
||||
open the scope at their final commit and record only the result they keep. A stopped run
|
||||
that keeps nothing records nothing; an interactive drag records one entry on mouse-up.
|
||||
- Repeated edits with an equal `coalesceKey` within one second undo as one (arrow-key nudges of
|
||||
one selection). An Undo, Redo or save in between ends the run.
|
||||
one selection, fixed or calculated). An Undo, Redo or save in between ends the run.
|
||||
|
||||
### The "Change" safety net
|
||||
|
||||
|
||||
Reference in new issue
Block a user