mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-26 22:01:06 -04:00
feat(planning): add a Cut planning effort setting that scales the search budget
Tools > Options > Cut planning effort (default 1.0, 0.5-10) multiplies the desktop cut planner's per-plate expansion budget for both the free-order search and the current-order retry. The budget counts expansions, not time, so the setting is how faster hardware can be spent on hard plates. The panel reads the setting on the UI thread and passes it to CuttingPlanBatch.Capture; the worker never reads settings. The 1,000- expansion first pass stays fixed: it is a cheap probe for easy plates, so a higher effort only adds work where the full search runs. A plate refused at the search limit now says the effort can be raised, without promising a plan. The default keeps today's budgets and characterization digests. Console, MCP and API do not run the cut planner, so they get no option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a10e204268
commit
56eaf4829a
9 files changed
+207
-20
No files matched your search
@@ -43,7 +43,18 @@ public sealed class CuttingPlanBatch
|
||||
/// </summary>
|
||||
public const int ExpansionsPerPart = 400;
|
||||
|
||||
/// <summary>The planning-effort multiplier that gives today's budgets.</summary>
|
||||
public const double DefaultBudgetMultiplier = 1.0;
|
||||
|
||||
/// <summary>The smallest planning-effort multiplier; it still leaves the first pass room to run.</summary>
|
||||
public const double MinimumBudgetMultiplier = 0.5;
|
||||
|
||||
/// <summary>The largest planning-effort multiplier, a bound on one plate's planning time.</summary>
|
||||
public const double MaximumBudgetMultiplier = 10.0;
|
||||
|
||||
// Extra bounded work on hard/refused plates; the full-cap search still gets its whole budget.
|
||||
// Not scaled by the multiplier: it is a fixed-cost probe for easy plates, so raising the
|
||||
// effort spends more only where the full search runs and leaves easy plates' plans unchanged.
|
||||
internal const int FirstPassBudget = 1000;
|
||||
internal const int FirstPassEntries = 8;
|
||||
|
||||
@@ -64,16 +75,26 @@ public sealed class CuttingPlanBatch
|
||||
/// current-order request is captured too, so a free search that runs out of budget can be
|
||||
/// retried on the worker without reading live plates again.
|
||||
/// </summary>
|
||||
/// <param name="budgetMultiplier">
|
||||
/// Scales both searches' <see cref="PlateBudget"/>, between <see cref="MinimumBudgetMultiplier"/>
|
||||
/// and <see cref="MaximumBudgetMultiplier"/>. The caller passes the operator's setting; the
|
||||
/// worker never reads it.
|
||||
/// </param>
|
||||
public static CuttingPlanBatch Capture(IReadOnlyList<Plate> plates, CuttingParameters confirmedParameters,
|
||||
bool preservePartOrder, IReadOnlyList<int> plateNumbers = null, CancellationToken token = default) =>
|
||||
Capture(plates, confirmedParameters, preservePartOrder, plateNumbers, null, token);
|
||||
bool preservePartOrder, IReadOnlyList<int> plateNumbers = null, CancellationToken token = default,
|
||||
double budgetMultiplier = DefaultBudgetMultiplier) =>
|
||||
Capture(plates, confirmedParameters, preservePartOrder, plateNumbers, null, token, budgetMultiplier);
|
||||
|
||||
/// <param name="reorderBudget">Overrides the new-order budget (tests); null gives <see cref="PlateBudget"/>.</param>
|
||||
internal static CuttingPlanBatch Capture(IReadOnlyList<Plate> plates, CuttingParameters confirmedParameters,
|
||||
bool preservePartOrder, IReadOnlyList<int> plateNumbers, int? reorderBudget, CancellationToken token)
|
||||
bool preservePartOrder, IReadOnlyList<int> plateNumbers, int? reorderBudget, CancellationToken token,
|
||||
double budgetMultiplier = DefaultBudgetMultiplier)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plates);
|
||||
ArgumentNullException.ThrowIfNull(confirmedParameters);
|
||||
if (!(budgetMultiplier >= MinimumBudgetMultiplier && budgetMultiplier <= MaximumBudgetMultiplier))
|
||||
throw new ArgumentOutOfRangeException(nameof(budgetMultiplier), budgetMultiplier,
|
||||
$"The budget multiplier must be between {MinimumBudgetMultiplier} and {MaximumBudgetMultiplier}.");
|
||||
if (plates.Count == 0)
|
||||
throw new ArgumentException("At least one plate is required.", nameof(plates));
|
||||
if (plates.Any(plate => plate == null)
|
||||
@@ -96,7 +117,7 @@ public sealed class CuttingPlanBatch
|
||||
for (var index = 0; index < plates.Count; index++)
|
||||
{
|
||||
var plate = plates[index];
|
||||
var budget = PlateBudget(plate.Parts.Count);
|
||||
var budget = PlateBudget(plate.Parts.Count, budgetMultiplier);
|
||||
var keepOrder = CuttingPlanService.Capture(CuttingPlanRequest.ForPlate(plate,
|
||||
expansionBudget: budget, confirmedParameters: confirmedParameters,
|
||||
preservePartOrder: true), token);
|
||||
@@ -112,9 +133,21 @@ public sealed class CuttingPlanBatch
|
||||
return new(entries, owned);
|
||||
}
|
||||
|
||||
internal static int PlateBudget(int partCount) =>
|
||||
(int)System.Math.Min(int.MaxValue,
|
||||
System.Math.Max((long)MinimumExpansionBudget, (long)partCount * ExpansionsPerPart));
|
||||
/// <summary>A plate's expansion budget: <see cref="ExpansionsPerPart"/> per part, at least
|
||||
/// <see cref="MinimumExpansionBudget"/>, times the multiplier.</summary>
|
||||
internal static int PlateBudget(int partCount, double multiplier = DefaultBudgetMultiplier)
|
||||
{
|
||||
var budget = System.Math.Max((double)MinimumExpansionBudget, (double)partCount * ExpansionsPerPart);
|
||||
return (int)System.Math.Min(int.MaxValue, System.Math.Round(budget * multiplier));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings a stored planning-effort value inside the supported range; a non-finite value gives
|
||||
/// <see cref="DefaultBudgetMultiplier"/>.
|
||||
/// </summary>
|
||||
public static double ClampBudgetMultiplier(double multiplier) => double.IsFinite(multiplier)
|
||||
? System.Math.Clamp(multiplier, MinimumBudgetMultiplier, MaximumBudgetMultiplier)
|
||||
: DefaultBudgetMultiplier;
|
||||
|
||||
/// <summary>
|
||||
/// Checks and plans every plate from its captured snapshots. Safe on a worker: live plates are
|
||||
@@ -232,6 +265,15 @@ public sealed class CuttingPlanPlateResult
|
||||
public bool CanApplyWithWarnings => Overlap != null && Overlap.Pairs.Count == 0
|
||||
&& (IsRouteReady || Result.Status == CuttingPlanStatus.BestEffort);
|
||||
|
||||
/// <summary>
|
||||
/// Refused after a search ran out of its expansion budget: this plan, or the free-order attempt
|
||||
/// before the current order was kept. A higher planning effort searches further, without a promise.
|
||||
/// </summary>
|
||||
public bool SearchLimitReached => !CanApplyWithWarnings && !IsRouteReady
|
||||
&& (Result.Status == CuttingPlanStatus.NoSolutionWithinBudget || KeptCurrentOrder
|
||||
&& Result.Status is not (CuttingPlanStatus.Cancelled or CuttingPlanStatus.UnsupportedGeometry
|
||||
or CuttingPlanStatus.InvalidInput));
|
||||
|
||||
/// <summary>
|
||||
/// The route was replayed and passed every strict check; only fixed emissions (locked or
|
||||
/// pinned leads) carry warnings, so the proposal is UNVERIFIED and needs acceptance.
|
||||
@@ -455,8 +497,14 @@ public sealed class CuttingPlanProposal
|
||||
+ "current order was refused:";
|
||||
foreach (var line in Limit(result.Findings.Select(DescribeFinding)))
|
||||
yield return line;
|
||||
if (!missingLead && plate.SearchLimitReached)
|
||||
yield return " " + SearchLimitHint;
|
||||
}
|
||||
|
||||
/// <summary>Operator hint for a plate refused at the search limit; raising the effort is not a promise.</summary>
|
||||
public const string SearchLimitHint = "Raising Cut planning effort in Tools > Options lets the planner search "
|
||||
+ "further before it stops; it may still find no plan.";
|
||||
|
||||
private static IEnumerable<string> Limit(IEnumerable<string> lines)
|
||||
{
|
||||
var all = lines.ToList();
|
||||
|
||||
@@ -222,6 +222,8 @@ public class CuttingPlanBatchTests
|
||||
Assert.False(planned.OrderChanged);
|
||||
Assert.Equal(order, planned.Result.ProposedOrder.Select(p => p.SourcePart));
|
||||
Assert.Contains("the current order is kept", string.Join("\n", proposal.Describe("in")));
|
||||
Assert.False(planned.SearchLimitReached); // A ready plate is not a refusal.
|
||||
Assert.DoesNotContain(CuttingPlanProposal.SearchLimitHint, string.Join("\n", proposal.Describe("in")));
|
||||
Assert.True(proposal.CanApply);
|
||||
}
|
||||
|
||||
@@ -335,6 +337,54 @@ public class CuttingPlanBatchTests
|
||||
public void PlateBudget_ScalesWithPartCountFromTheDefault(int parts, int expected) =>
|
||||
Assert.Equal(expected, CuttingPlanBatch.PlateBudget(parts));
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 1.0, 20000)]
|
||||
[InlineData(100, 1.0, 40000)]
|
||||
[InlineData(10, 0.5, 10000)]
|
||||
[InlineData(10, 2.5, 50000)]
|
||||
[InlineData(100, 10.0, 400000)]
|
||||
[InlineData(6_000_000, 10.0, int.MaxValue)]
|
||||
public void PlateBudget_ScalesWithTheMultiplier(int parts, double multiplier, int expected) =>
|
||||
Assert.Equal(expected, CuttingPlanBatch.PlateBudget(parts, multiplier));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1.0, 20000)]
|
||||
[InlineData(2.5, 50000)]
|
||||
public void Capture_BudgetMultiplier_ScalesTheSearchBudgetItPlansWith(double multiplier, int expected)
|
||||
{
|
||||
var plate = Plate(new Nest(), Clean("a", 1, 1), Clean("b", 12, 1));
|
||||
|
||||
var proposal = CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), preservePartOrder: true,
|
||||
budgetMultiplier: multiplier).Plan();
|
||||
|
||||
var planned = Assert.Single(proposal.Plates);
|
||||
Assert.True(planned.IsReady);
|
||||
Assert.Equal(expected, planned.Result.Snapshot.ExpansionBudget);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.4)]
|
||||
[InlineData(10.1)]
|
||||
[InlineData(double.NaN)]
|
||||
[InlineData(double.PositiveInfinity)]
|
||||
public void Capture_BudgetMultiplierOutOfRange_Throws(double multiplier)
|
||||
{
|
||||
var plate = Plate(new Nest(), Clean("a", 1, 1));
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), false, budgetMultiplier: multiplier));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1.0, 1.0)]
|
||||
[InlineData(3.5, 3.5)]
|
||||
[InlineData(0.0, 0.5)]
|
||||
[InlineData(50.0, 10.0)]
|
||||
[InlineData(double.NaN, 1.0)]
|
||||
[InlineData(double.NegativeInfinity, 1.0)]
|
||||
public void ClampBudgetMultiplier_KeepsStoredValuesInRange(double stored, double expected) =>
|
||||
Assert.Equal(expected, CuttingPlanBatch.ClampBudgetMultiplier(stored));
|
||||
|
||||
[Fact]
|
||||
public void Plan_LargeGrid_FindsANewOrderWithinThePlateBudget()
|
||||
{
|
||||
|
||||
@@ -161,8 +161,24 @@ public class CuttingPlanMessageTests
|
||||
Assert.Contains("Plate 7: no complete plan was found within the search limit.", text);
|
||||
Assert.Contains("planning with the current order was refused:", text);
|
||||
Assert.Contains(finding.Message, text);
|
||||
Assert.Contains(CuttingPlanProposal.SearchLimitHint, text);
|
||||
Assert.Contains("it may still find no plan", CuttingPlanProposal.SearchLimitHint);
|
||||
Assert.True(proposal.Plates[0].SearchLimitReached);
|
||||
Assert.DoesNotContain("Open Cutting Settings...", text);
|
||||
Assert.DoesNotContain("missing or zero-length lead-in", text);
|
||||
Assert.False(proposal.CanApply);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CuttingPlanStatus.UnsupportedGeometry)]
|
||||
[InlineData(CuttingPlanStatus.Cancelled)]
|
||||
public void Describe_KeptOrderRefusalAMoreEffortCannotFix_OmitsTheSearchLimitHint(CuttingPlanStatus status)
|
||||
{
|
||||
var attempt = new CuttingPlanResult(CuttingPlanStatus.NoSolutionWithinBudget);
|
||||
var proposal = new CuttingPlanProposal([new(new Nest().CreatePlate(), 1, new(status), attempt, null)],
|
||||
ExplicitContourTests.Parameters());
|
||||
|
||||
Assert.False(proposal.Plates[0].SearchLimitReached);
|
||||
Assert.DoesNotContain(CuttingPlanProposal.SearchLimitHint, string.Join("\n", proposal.Describe("in")));
|
||||
}
|
||||
}
|
||||
@@ -617,7 +617,8 @@ internal sealed class CuttingInspectionPanel : UserControl
|
||||
try
|
||||
{
|
||||
// Capture on the owner thread; no worker recapture of the live plate or settings.
|
||||
var batch = CuttingPlanBatch.Capture(plates, parameters, keepOrderCheckBox.Checked, plateNumbers);
|
||||
var batch = CuttingPlanBatch.Capture(plates, parameters, keepOrderCheckBox.Checked, plateNumbers,
|
||||
budgetMultiplier: CuttingPlanBatch.ClampBudgetMultiplier(Properties.Settings.Default.CuttingPlanEffort));
|
||||
var run = ++generation;
|
||||
var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
PlanningTask = completion.Task;
|
||||
@@ -790,6 +791,8 @@ internal sealed class CuttingInspectionPanel : UserControl
|
||||
return "Move the overlapping parts apart, then Replan.";
|
||||
if (!plate.IsRouteReady && plate.Result.Findings.Any(f => f.Kind == PostVerificationKind.MissingLeadIn))
|
||||
return "In Cutting Settings, choose a lead-in with a nonzero length (or unlock the part), then Replan.";
|
||||
if (plate.SearchLimitReached)
|
||||
return "Raise Cut planning effort in Tools > Options to search further, then Replan; it may still find no plan.";
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Generated
+48
-12
@@ -33,6 +33,8 @@
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.numericUpDown1 = new OpenNest.Controls.NumericUpDown();
|
||||
this.planningEffortLabel = new System.Windows.Forms.Label();
|
||||
this.planningEffortBox = new OpenNest.Controls.NumericUpDown();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.saveButton = new System.Windows.Forms.Button();
|
||||
this.cancelButton = new System.Windows.Forms.Button();
|
||||
@@ -48,6 +50,7 @@
|
||||
this.updateFeedUrlBox = new System.Windows.Forms.TextBox();
|
||||
this.setUpdateFeedButton = new System.Windows.Forms.Button();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.planningEffortBox)).BeginInit();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.bottomPanel1.SuspendLayout();
|
||||
this.serverGroupBox.SuspendLayout();
|
||||
@@ -59,7 +62,7 @@
|
||||
this.checkBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.checkBox1.AutoSize = true;
|
||||
this.tableLayoutPanel1.SetColumnSpan(this.checkBox1, 2);
|
||||
this.checkBox1.Location = new System.Drawing.Point(3, 130);
|
||||
this.checkBox1.Location = new System.Drawing.Point(3, 165);
|
||||
this.checkBox1.Name = "checkBox1";
|
||||
this.checkBox1.Size = new System.Drawing.Size(281, 20);
|
||||
this.checkBox1.TabIndex = 7;
|
||||
@@ -92,6 +95,33 @@
|
||||
this.numericUpDown1.TabIndex = 4;
|
||||
this.toolTip1.SetToolTip(this.numericUpDown1, "The amount to round the plate size up.");
|
||||
//
|
||||
// planningEffortLabel
|
||||
//
|
||||
this.planningEffortLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.planningEffortLabel.AutoSize = true;
|
||||
this.planningEffortLabel.Location = new System.Drawing.Point(3, 117);
|
||||
this.planningEffortLabel.Name = "planningEffortLabel";
|
||||
this.planningEffortLabel.Size = new System.Drawing.Size(145, 16);
|
||||
this.planningEffortLabel.TabIndex = 12;
|
||||
this.planningEffortLabel.Text = "Cut planning effort:";
|
||||
//
|
||||
// planningEffortBox
|
||||
//
|
||||
this.planningEffortBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.planningEffortBox.DecimalPlaces = 1;
|
||||
this.planningEffortBox.Increment = new decimal(new int[] {
|
||||
5,
|
||||
0,
|
||||
0,
|
||||
65536});
|
||||
this.planningEffortBox.Location = new System.Drawing.Point(154, 114);
|
||||
this.planningEffortBox.Name = "planningEffortBox";
|
||||
this.planningEffortBox.Size = new System.Drawing.Size(130, 22);
|
||||
this.planningEffortBox.Suffix = "";
|
||||
this.planningEffortBox.TabIndex = 13;
|
||||
this.toolTip1.SetToolTip(this.planningEffortBox, "Multiplies the cut planner's search limit (1 = standard). "
|
||||
+ "Higher values search longer on hard plates; results are the same on every computer.");
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
this.tableLayoutPanel1.ColumnCount = 4;
|
||||
@@ -102,15 +132,18 @@
|
||||
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.colorSchemeLabel, 0, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.colorSchemeCombo, 1, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.checkBox1, 0, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.planningEffortLabel, 0, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.planningEffortBox, 1, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.checkBox1, 0, 3);
|
||||
this.tableLayoutPanel1.Controls.Add(this.numericUpDown1, 1, 0);
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 12);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
this.tableLayoutPanel1.RowCount = 3;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(684, 160);
|
||||
this.tableLayoutPanel1.RowCount = 4;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(684, 200);
|
||||
this.tableLayoutPanel1.TabIndex = 0;
|
||||
//
|
||||
// saveButton
|
||||
@@ -143,7 +176,7 @@
|
||||
this.bottomPanel1.Controls.Add(this.cancelButton);
|
||||
this.bottomPanel1.Controls.Add(this.saveButton);
|
||||
this.bottomPanel1.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.bottomPanel1.Location = new System.Drawing.Point(0, 509);
|
||||
this.bottomPanel1.Location = new System.Drawing.Point(0, 549);
|
||||
this.bottomPanel1.Name = "bottomPanel1";
|
||||
this.bottomPanel1.Size = new System.Drawing.Size(708, 50);
|
||||
this.bottomPanel1.TabIndex = 1;
|
||||
@@ -162,7 +195,7 @@
|
||||
// strategyGroupBox
|
||||
//
|
||||
this.strategyGroupBox.Controls.Add(this.strategyGrid);
|
||||
this.strategyGroupBox.Location = new System.Drawing.Point(12, 319);
|
||||
this.strategyGroupBox.Location = new System.Drawing.Point(12, 359);
|
||||
this.strategyGroupBox.Name = "strategyGroupBox";
|
||||
this.strategyGroupBox.Size = new System.Drawing.Size(684, 180);
|
||||
this.strategyGroupBox.TabIndex = 2;
|
||||
@@ -192,7 +225,7 @@
|
||||
//
|
||||
this.serverGroupBox.Controls.Add(this.serverUrlBox);
|
||||
this.serverGroupBox.Controls.Add(this.setServerButton);
|
||||
this.serverGroupBox.Location = new System.Drawing.Point(12, 178);
|
||||
this.serverGroupBox.Location = new System.Drawing.Point(12, 218);
|
||||
this.serverGroupBox.Name = "serverGroupBox";
|
||||
this.serverGroupBox.Size = new System.Drawing.Size(684, 69);
|
||||
this.serverGroupBox.TabIndex = 1;
|
||||
@@ -221,7 +254,7 @@
|
||||
//
|
||||
this.updateGroupBox.Controls.Add(this.updateFeedUrlBox);
|
||||
this.updateGroupBox.Controls.Add(this.setUpdateFeedButton);
|
||||
this.updateGroupBox.Location = new System.Drawing.Point(12, 253);
|
||||
this.updateGroupBox.Location = new System.Drawing.Point(12, 293);
|
||||
this.updateGroupBox.Name = "updateGroupBox";
|
||||
this.updateGroupBox.Size = new System.Drawing.Size(684, 60);
|
||||
this.updateGroupBox.TabIndex = 3;
|
||||
@@ -251,7 +284,7 @@
|
||||
this.AcceptButton = this.saveButton;
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.ClientSize = new System.Drawing.Size(708, 559);
|
||||
this.ClientSize = new System.Drawing.Size(708, 599);
|
||||
this.Controls.Add(this.strategyGroupBox);
|
||||
this.Controls.Add(this.updateGroupBox);
|
||||
this.Controls.Add(this.serverGroupBox);
|
||||
@@ -267,6 +300,7 @@
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Options";
|
||||
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.planningEffortBox)).EndInit();
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
this.tableLayoutPanel1.PerformLayout();
|
||||
this.bottomPanel1.ResumeLayout(false);
|
||||
@@ -285,6 +319,8 @@
|
||||
private System.Windows.Forms.Button saveButton;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private Controls.NumericUpDown numericUpDown1;
|
||||
private System.Windows.Forms.Label planningEffortLabel;
|
||||
private Controls.NumericUpDown planningEffortBox;
|
||||
private System.Windows.Forms.ToolTip toolTip1;
|
||||
private Controls.BottomPanel bottomPanel1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Engine.CuttingPlanning;
|
||||
using OpenNest.Engine.Strategies;
|
||||
using OpenNest.Properties;
|
||||
|
||||
@@ -14,6 +15,8 @@ namespace OpenNest.Forms
|
||||
public OptionsForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
planningEffortBox.Minimum = (decimal)CuttingPlanBatch.MinimumBudgetMultiplier;
|
||||
planningEffortBox.Maximum = (decimal)CuttingPlanBatch.MaximumBudgetMultiplier;
|
||||
GridStyle.Apply(strategyGrid);
|
||||
BuildStrategyGrid();
|
||||
}
|
||||
@@ -79,6 +82,7 @@ namespace OpenNest.Forms
|
||||
updateFeedUrlBox.Text = Settings.Default.UpdateFeedUrl;
|
||||
checkBox1.Checked = Settings.Default.CreateNewNestOnOpen;
|
||||
numericUpDown1.Value = (decimal)Settings.Default.AutoSizePlateFactor;
|
||||
planningEffortBox.Value = (decimal)CuttingPlanBatch.ClampBudgetMultiplier(Settings.Default.CuttingPlanEffort);
|
||||
|
||||
colorSchemeCombo.Items.Clear();
|
||||
foreach (var scheme in ColorSchemeRegistry.AllSchemes)
|
||||
@@ -98,6 +102,7 @@ namespace OpenNest.Forms
|
||||
{
|
||||
Settings.Default.CreateNewNestOnOpen = checkBox1.Checked;
|
||||
Settings.Default.AutoSizePlateFactor = (double)numericUpDown1.Value;
|
||||
Settings.Default.CuttingPlanEffort = (double)planningEffortBox.Value;
|
||||
Settings.Default.ActiveColorScheme =
|
||||
colorSchemeCombo.SelectedItem as string ?? "Classic";
|
||||
|
||||
|
||||
Generated
+13
@@ -345,5 +345,18 @@ namespace OpenNest.Properties {
|
||||
this["ClampDimensionToCenter"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("1")]
|
||||
// Cut planner budget multiplier; 1 keeps the built-in budgets.
|
||||
public double CuttingPlanEffort {
|
||||
get {
|
||||
return ((double)(this["CuttingPlanEffort"]));
|
||||
}
|
||||
set {
|
||||
this["CuttingPlanEffort"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,5 +83,8 @@
|
||||
<Setting Name="ClampDimensionToCenter" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="CuttingPlanEffort" Type="System.Double" Scope="User">
|
||||
<Value Profile="(Default)">1</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -615,6 +615,19 @@ panel is wired in the editor. Non-editor callers still use the shared analyzer.
|
||||
`NoSolutionWithinBudget` is retried once with the current part order, and the summary
|
||||
says the order was kept. Both are allowed 400 expansions per part (at least the
|
||||
default 20000), because both still plan contour order and entries for every part.
|
||||
- **Tools > Options > Cut planning effort** (`Settings.CuttingPlanEffort`, default 1.0,
|
||||
0.5–10) multiplies both of those budgets. The panel reads it when planning starts and
|
||||
passes it to `CuttingPlanBatch.Capture(..., budgetMultiplier)`; the worker never reads
|
||||
settings, and out-of-range stored values are clamped. The budget counts expansions, not
|
||||
time, so a given effort gives the same plan on every computer; faster hardware only
|
||||
finishes it sooner, and raising the effort is how to spend that speed on hard plates.
|
||||
The eight-entry, 1,000-expansion first pass is not scaled: it is a cheap probe for easy
|
||||
plates, so a higher effort spends more only where the full search runs and leaves easy
|
||||
plates' plans unchanged. A plate refused because a search reached its limit (its own
|
||||
plan, or the free-order attempt before the current order was kept) says the effort can
|
||||
be raised; a longer search may still find no plan. Only the desktop plans cuts: the
|
||||
console, MCP server and API create plates but do not call the planner, so they have no
|
||||
budget to configure. Direct `CuttingPlanRequest` callers set `expansionBudget` themselves.
|
||||
- The docked panel keeps text short: one colored outcome line for the batch, one line for the
|
||||
shown plate, and one detail line (a remedy for a blocked plate, or what the selected marker
|
||||
shows). Below them is a read-only, unsorted issue grid with Plate, source Part, With, and full
|
||||
|
||||
Reference in new issue
Block a user