mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 08:21:05 -04:00
feat(desktop): remove or restore a clamp by right-click
Operators sometimes pull a clamp for a manual layout, but only Auto Nest's last-resort search could take clamps off a sheet. Right-clicking a clamp in Select mode now offers to remove it from that sheet or put it back, reusing the existing per-sheet RemovedClamps, so the zones, cut-time charge, display and .nest persistence all behave as they do for Auto Nest removals. The change is refused while a nest run or fill may be reading the clamps, sharing the guard the Clamps dialog already used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d028d82f0a
commit
7d254ce18d
9 files changed
+371
-15
No files matched your search
+41
-2
@@ -145,8 +145,9 @@ namespace OpenNest
|
||||
private static readonly IReadOnlySet<string> NoRemovedClamps = ImmutableHashSet<string>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Rail clamp ids taken off this sheet as a last resort (D8) so a part that could not
|
||||
/// be nested anywhere with every clamp in place still fits. Empty unless
|
||||
/// Rail clamp ids taken off this sheet, by Auto Nest as a last resort (D8) so a part
|
||||
/// that could not be nested anywhere with every clamp in place still fits, or by hand
|
||||
/// (<see cref="SetClampRemoved"/>). Empty unless
|
||||
/// <see cref="ClampRail"/> is set; <see cref="ClampZones"/> already excludes them, so
|
||||
/// nothing else needs to consult this list to avoid them. Ids stay listed across a
|
||||
/// resize/rotate even when the matching clamp is temporarily beyond the sheet, so it
|
||||
@@ -188,6 +189,44 @@ namespace OpenNest
|
||||
ReplaceClampZones(ResolveRailZones());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rail clamp this sheet places at <paramref name="point"/> (the parts' placement
|
||||
/// frame, see <see cref="ClampZoneFrame"/>), whether in place or removed, grown by
|
||||
/// <paramref name="tolerance"/> on every side; null without a rail or a clamp there.
|
||||
/// Returned bounds are in the placement frame.
|
||||
/// </summary>
|
||||
public ClampZone RailClampAt(Vector point, double tolerance = 0)
|
||||
{
|
||||
if (ClampRail == null)
|
||||
return null;
|
||||
var left = Quadrant is 2 or 3 ? -Size.Length : 0;
|
||||
var bottom = Quadrant is 3 or 4 ? -Size.Width : 0;
|
||||
foreach (var zone in ClampRail.ResolveFor(Size, Quadrant))
|
||||
{
|
||||
var box = zone.Bounds.Translate(left, bottom);
|
||||
if (box.Offset(tolerance).Contains(point))
|
||||
return new ClampZone(zone.Id, box);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the rail clamp <paramref name="id"/> off this sheet, or puts it back, as a
|
||||
/// manual counterpart to Auto Nest's last-resort removal (D8): the zones and
|
||||
/// <see cref="ClampRemovalMinutes"/> follow. False when nothing changed: no rail, a clamp
|
||||
/// the rail does not place on this sheet, or one already in the requested state.
|
||||
/// </summary>
|
||||
public bool SetClampRemoved(string id, bool removed)
|
||||
{
|
||||
if (ClampRail == null || id == null || RemovedClamps.Contains(id) == removed)
|
||||
return false;
|
||||
if (removed && !ClampRail.ResolveFor(Size, Quadrant).Any(zone => zone.Id == id))
|
||||
return false;
|
||||
var ids = removed ? RemovedClamps.Append(id) : RemovedClamps.Where(x => x != id);
|
||||
SetClampRail(ClampRail, ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResolveClampRail()
|
||||
{
|
||||
// Called from the constructor's Size assignment before the zone list exists.
|
||||
|
||||
@@ -395,4 +395,104 @@ public class ClampRailTests
|
||||
Assert.True(((ICollection<string>)removed.RemovedClamps).IsReadOnly);
|
||||
Assert.True(((ICollection<string>)untouched.RemovedClamps).IsReadOnly);
|
||||
}
|
||||
|
||||
// Manual per-sheet removal (right-click in the plate view).
|
||||
|
||||
[Fact]
|
||||
public void SetClampRemovedTakesAClampOffAndPutsItBack()
|
||||
{
|
||||
var plate = Sheet(48, 120);
|
||||
plate.SetClampRail(Machine());
|
||||
var changes = 0;
|
||||
plate.ClampZonesChanged += (_, _) => changes++;
|
||||
|
||||
Assert.True(plate.SetClampRemoved("clamp-3", true));
|
||||
Assert.Equal(new[] { "clamp-3" }, plate.RemovedClamps);
|
||||
Assert.DoesNotContain(plate.ClampZones, z => z.Id == "clamp-3");
|
||||
Assert.Equal(ClampRail.DefaultRemovalMinutes, plate.ClampRemovalMinutes);
|
||||
Assert.True(changes > 0);
|
||||
|
||||
Assert.True(plate.SetClampRemoved("clamp-3", false));
|
||||
Assert.Empty(plate.RemovedClamps);
|
||||
Assert.True(Machine().Matches(plate));
|
||||
Assert.Equal(0, plate.ClampRemovalMinutes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetClampRemovedKeepsOtherRemovedClamps()
|
||||
{
|
||||
var plate = Sheet(48, 120);
|
||||
plate.SetClampRail(Machine(), new[] { "clamp-1" });
|
||||
|
||||
Assert.True(plate.SetClampRemoved("clamp-3", true));
|
||||
Assert.Equal(new[] { "clamp-1", "clamp-3" }, plate.RemovedClamps.OrderBy(id => id));
|
||||
|
||||
Assert.True(plate.SetClampRemoved("clamp-1", false));
|
||||
Assert.Equal(new[] { "clamp-3" }, plate.RemovedClamps);
|
||||
Assert.Equal(Machine(), plate.ClampRail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetClampRemovedReportsNoChange()
|
||||
{
|
||||
var bare = Sheet(48, 120);
|
||||
Assert.False(bare.SetClampRemoved("clamp-1", true)); // no rail
|
||||
|
||||
var plate = Sheet(48, 60);
|
||||
plate.SetClampRail(Machine());
|
||||
Assert.False(plate.SetClampRemoved("clamp-1", false)); // already in place
|
||||
Assert.False(plate.SetClampRemoved("clamp-8", true)); // beyond this sheet
|
||||
Assert.False(plate.SetClampRemoved("clamp-99", true)); // unknown to the rail
|
||||
Assert.False(plate.SetClampRemoved(null!, true));
|
||||
Assert.Empty(plate.RemovedClamps);
|
||||
|
||||
Assert.True(plate.SetClampRemoved("clamp-2", true));
|
||||
Assert.False(plate.SetClampRemoved("clamp-2", true)); // already removed
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ARemovedClampBeyondTheSheetCanStillBePutBack()
|
||||
{
|
||||
var plate = Sheet(48, 120);
|
||||
plate.SetClampRail(Machine(), new[] { "clamp-8" });
|
||||
plate.Size = new Size(48, 60);
|
||||
|
||||
Assert.True(plate.SetClampRemoved("clamp-8", false));
|
||||
Assert.Empty(plate.RemovedClamps);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
public void RailClampAtFindsClampsInThePartsPlacementFrame(int quadrant)
|
||||
{
|
||||
var plate = Sheet(48, 120, quadrant);
|
||||
plate.SetClampRail(Machine(), new[] { "clamp-3" });
|
||||
var full = Sheet(48, 120, quadrant);
|
||||
full.SetClampRail(Machine());
|
||||
var expected = ClampZoneFrame.ToPlacement(full); // every clamp, removed ones too
|
||||
|
||||
foreach (var zone in expected)
|
||||
{
|
||||
var hit = plate.RailClampAt(zone.Bounds.Center);
|
||||
Assert.NotNull(hit);
|
||||
Assert.Equal(zone.Id, hit.Id);
|
||||
Assert.Equal(zone.Bounds.X, hit.Bounds.X, 9);
|
||||
Assert.Equal(zone.Bounds.Y, hit.Bounds.Y, 9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RailClampAtHonoursTheToleranceAndNeedsARail()
|
||||
{
|
||||
var plate = Sheet(48, 120);
|
||||
Assert.Null(plate.RailClampAt(new Vector(36, 0.3))); // no rail
|
||||
|
||||
plate.SetClampRail(Machine());
|
||||
Assert.Equal("clamp-3", plate.RailClampAt(new Vector(36, 0.3))?.Id);
|
||||
Assert.Null(plate.RailClampAt(new Vector(36, 0.7)));
|
||||
Assert.Equal("clamp-3", plate.RailClampAt(new Vector(36, 0.7), tolerance: 0.1)?.Id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using OpenNest.Actions;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Controls;
|
||||
|
||||
/// <summary>Right-click on a machine clamp takes it off the current sheet or puts it back.</summary>
|
||||
public class PlateViewClampMenuTests
|
||||
{
|
||||
// Default view scale is about one pixel per inch: allow for rounding to whole pixels.
|
||||
private const double Tolerance = 1.0;
|
||||
|
||||
private static readonly double[] Centers = { 6, 21, 36, 51, 66, 81, 96, 111 };
|
||||
|
||||
private static Plate RailPlate()
|
||||
{
|
||||
var plate = new Plate(48, 120);
|
||||
plate.SetClampRail(new ClampRail(1.5, 0.625, Centers));
|
||||
return plate;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemovesAClampAndPutsItBack() => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = RailPlate();
|
||||
using var view = new PlateView { Plate = plate };
|
||||
var onClamp3 = view.PointWorldToControl(new Vector(36, 0.3125));
|
||||
|
||||
var menu = view.ClampMenu.BuildMenu(onClamp3, Tolerance);
|
||||
Assert.NotNull(menu);
|
||||
var remove = Assert.Single(menu.Items.Cast<System.Windows.Forms.ToolStripItem>());
|
||||
Assert.Equal("Remove clamp-3 from This Sheet", remove.Text);
|
||||
|
||||
remove.PerformClick();
|
||||
Assert.Equal(new[] { "clamp-3" }, plate.RemovedClamps);
|
||||
Assert.DoesNotContain(plate.ClampZones, zone => zone.Id == "clamp-3");
|
||||
Assert.StartsWith("Removed clamp-3", view.Status);
|
||||
|
||||
// A removed clamp is still hit (it is drawn dashed) and offers to come back.
|
||||
var restore = Assert.Single(
|
||||
view.ClampMenu.BuildMenu(onClamp3, Tolerance)!.Items.Cast<System.Windows.Forms.ToolStripItem>());
|
||||
Assert.Equal("Put clamp-3 Back on This Sheet", restore.Text);
|
||||
|
||||
restore.PerformClick();
|
||||
Assert.Empty(plate.RemovedClamps);
|
||||
Assert.Equal(8, plate.ClampZones.Count);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
|
||||
[Fact]
|
||||
public void OffersNothingAwayFromAClampOrWhenClampsCannotBeEdited() => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = RailPlate();
|
||||
using var view = new PlateView { Plate = plate };
|
||||
var onClamp3 = view.PointWorldToControl(new Vector(36, 0.3125));
|
||||
|
||||
Assert.Null(view.ClampMenu.BuildMenu(view.PointWorldToControl(new Vector(28, 20)), Tolerance));
|
||||
|
||||
view.InspectionOnly = true;
|
||||
Assert.Null(view.ClampMenu.BuildMenu(onClamp3, Tolerance));
|
||||
view.InspectionOnly = false;
|
||||
|
||||
view.Actions.SetAction(typeof(ActionZoomWindow));
|
||||
Assert.Null(view.ClampMenu.BuildMenu(onClamp3, Tolerance));
|
||||
view.Actions.SetAction(typeof(ActionSelect));
|
||||
|
||||
view.Plate = new Plate(48, 120); // no rail
|
||||
Assert.Null(view.ClampMenu.BuildMenu(onClamp3, Tolerance));
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
|
||||
[Fact]
|
||||
public void IgnoresAClickFromAMenuOpenedOnAnotherSheet() => StaTestThread.Run(() =>
|
||||
{
|
||||
var plate = RailPlate();
|
||||
using var view = new PlateView { Plate = plate };
|
||||
var menu = view.ClampMenu.BuildMenu(view.PointWorldToControl(new Vector(36, 0.3125)), Tolerance);
|
||||
var remove = menu!.Items[0];
|
||||
|
||||
view.Plate = RailPlate();
|
||||
remove.PerformClick();
|
||||
|
||||
Assert.Empty(plate.RemovedClamps);
|
||||
Assert.Empty(view.Plate.RemovedClamps);
|
||||
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Actions;
|
||||
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Right-click on a machine clamp in a <see cref="PlateView"/>: takes that clamp off the
|
||||
/// current sheet or puts it back (<see cref="Plate.SetClampRemoved"/>). Only in plate
|
||||
/// select mode, never while inspecting or previewing a proposal; the change itself is
|
||||
/// refused while a fill runs or <see cref="PlateView.CanEditClamps"/> says a nest run is
|
||||
/// using the clamps.
|
||||
/// </summary>
|
||||
internal sealed class ClampMenuHandler : IDisposable
|
||||
{
|
||||
private readonly PlateView view;
|
||||
private ContextMenuStrip menu;
|
||||
|
||||
public ClampMenuHandler(PlateView view)
|
||||
{
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
/// <summary>Shows the menu when <paramref name="location"/> is on a rail clamp.</summary>
|
||||
public bool TryShow(Point location, double tolerance)
|
||||
{
|
||||
if (BuildMenu(location, tolerance) == null)
|
||||
return false;
|
||||
menu.Show(view, location);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The menu for the rail clamp at <paramref name="location"/> (control coordinates),
|
||||
/// or null when there is none or clamps cannot be edited from here.
|
||||
/// </summary>
|
||||
internal ContextMenuStrip BuildMenu(Point location, double tolerance)
|
||||
{
|
||||
var plate = view.Plate;
|
||||
if (
|
||||
plate?.ClampRail == null
|
||||
|| !view.AllowSelect
|
||||
|| view.InspectionOnly
|
||||
|| view.HasProposalDisplay
|
||||
|| view.Actions.CurrentAction is not ActionSelect
|
||||
)
|
||||
return null;
|
||||
|
||||
var clamp = plate.RailClampAt(view.PointControlToWorld(location), tolerance);
|
||||
if (clamp == null)
|
||||
return null;
|
||||
|
||||
var removed = plate.RemovedClamps.Contains(clamp.Id);
|
||||
menu ??= new ContextMenuStrip();
|
||||
while (menu.Items.Count > 0)
|
||||
menu.Items[0].Dispose();
|
||||
var item = menu.Items.Add(
|
||||
removed ? $"Put {clamp.Id} Back on This Sheet" : $"Remove {clamp.Id} from This Sheet"
|
||||
);
|
||||
item.Click += (_, _) => Apply(plate, clamp.Id, !removed);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void Apply(Plate plate, string id, bool remove)
|
||||
{
|
||||
// The view may have moved to another sheet while the menu was open.
|
||||
if (!ReferenceEquals(plate, view.Plate))
|
||||
return;
|
||||
|
||||
// Clamps feed nesting and validation: never change them under a running operation.
|
||||
if (view.IsFillInProgress || view.CanEditClamps?.Invoke() == false)
|
||||
{
|
||||
MessageBox.Show(
|
||||
view.FindForm(),
|
||||
"Finish or cancel the current nesting or plate action first.",
|
||||
"Clamps",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plate.SetClampRemoved(id, remove))
|
||||
return;
|
||||
|
||||
view.Status = remove
|
||||
? $"Removed {id} from this sheet (+{plate.ClampRail.RemovalMinutes:0.#} min cut time)"
|
||||
: $"Put {id} back on this sheet";
|
||||
view.Invalidate();
|
||||
}
|
||||
|
||||
public void Dispose() => menu?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,8 @@ namespace OpenNest.Controls
|
||||
}
|
||||
}
|
||||
|
||||
// D8: a clamp Auto Nest pulled as a last resort. Distinct from an active clamp - a
|
||||
// D8: a clamp taken off this sheet, by Auto Nest as a last resort or by hand (right-click).
|
||||
// Distinct from an active clamp - a
|
||||
// dashed grey outline and a small "remove" marker, still display + exclusion only
|
||||
// (no hit test, selection or sequencing).
|
||||
private static readonly Color RemovedClampOutlineColor = Color.FromArgb(200, 110, 110, 110);
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace OpenNest.Controls
|
||||
private CutOffSettings cutOffSettings = new CutOffSettings();
|
||||
private SelectionManager selection;
|
||||
private CutOffHandler cutOffHandler;
|
||||
private readonly ClampMenuHandler clampMenu;
|
||||
private PreviewManager previewManager;
|
||||
protected List<LayoutPart> parts;
|
||||
private Point middleMouseDownPoint;
|
||||
@@ -72,6 +73,7 @@ namespace OpenNest.Controls
|
||||
|
||||
internal SelectionManager Selection => selection;
|
||||
internal CutOffHandler CutOffs => cutOffHandler;
|
||||
internal ClampMenuHandler ClampMenu => clampMenu;
|
||||
internal ActionManager Actions => actionManager;
|
||||
internal PreviewManager Previews => previewManager;
|
||||
|
||||
@@ -109,6 +111,7 @@ namespace OpenNest.Controls
|
||||
parts = new List<LayoutPart>();
|
||||
selection = new SelectionManager(this);
|
||||
cutOffHandler = new CutOffHandler(this);
|
||||
clampMenu = new ClampMenuHandler(this);
|
||||
previewManager = new PreviewManager(this);
|
||||
|
||||
redrawTimer = new Timer()
|
||||
@@ -156,6 +159,12 @@ namespace OpenNest.Controls
|
||||
internal bool InspectionOnly { get; set; }
|
||||
internal System.Action InspectionCloseRequested { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Asked before a right-click changes which clamps are on the sheet; false refuses
|
||||
/// (e.g. a nest run is using the clamps). Null allows.
|
||||
/// </summary>
|
||||
internal Func<bool> CanEditClamps { get; set; }
|
||||
|
||||
public bool AllowZoom { get; set; }
|
||||
|
||||
public bool AllowSelect { get; set; }
|
||||
@@ -476,6 +485,9 @@ namespace OpenNest.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Button == MouseButtons.Right)
|
||||
clampMenu.TryShow(e.Location, 3.0 / ViewScale);
|
||||
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
@@ -769,7 +781,10 @@ namespace OpenNest.Controls
|
||||
plate.PartAdded -= plate_PartAdded;
|
||||
plate.PartRemoved -= plate_PartRemoved;
|
||||
plate.PartsReordered -= plate_PartsReordered;
|
||||
plate.ClampZonesChanged -= plate_ClampZonesChanged;
|
||||
}
|
||||
clampMenu?.Dispose();
|
||||
CanEditClamps = null;
|
||||
overlapOverlay?.Dispose();
|
||||
validationOverlay?.Dispose();
|
||||
hoverTimer?.Dispose();
|
||||
|
||||
+20
-11
@@ -665,6 +665,8 @@ namespace OpenNest.Forms
|
||||
activeForm.PlateView.SelectionChanged += PlateView_SelectionChanged;
|
||||
activeForm.PlateView.PartAdded += PlateView_PartAdded;
|
||||
activeForm.PlateView.PartRemoved += PlateView_PartRemoved;
|
||||
var activeNest = activeForm.Nest;
|
||||
activeForm.PlateView.CanEditClamps = () => !ClampsInUse(activeNest);
|
||||
mnuViewDrawRapids.Checked = activeForm.PlateView.DrawRapid;
|
||||
mnuViewDrawPiercePoints.Checked = activeForm.PlateView.DrawPiercePoints;
|
||||
mnuViewDrawBounds.Checked = activeForm.PlateView.DrawBounds;
|
||||
@@ -1560,6 +1562,22 @@ namespace OpenNest.Forms
|
||||
activeForm.ShowNestInfoEditor();
|
||||
}
|
||||
|
||||
private PlateView[] ViewsOf(Nest nest) =>
|
||||
MdiChildren
|
||||
.OfType<EditNestForm>()
|
||||
.Where(form => ReferenceEquals(form.Nest, nest))
|
||||
.Select(form => form.PlateView)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// True while a nest run or fill may be reading <paramref name="nest"/>'s clamps.
|
||||
/// Clamps feed nesting and validation: never change them under a running operation.
|
||||
/// </summary>
|
||||
private bool ClampsInUse(Nest nest) =>
|
||||
nestingInProgress
|
||||
|| Application.OpenForms.OfType<NestProgressForm>().Any()
|
||||
|| ViewsOf(nest).Any(v => v.IsFillInProgress);
|
||||
|
||||
private void Clamps_Click(object sender, EventArgs e)
|
||||
{
|
||||
var editForm = activeForm;
|
||||
@@ -1567,17 +1585,8 @@ namespace OpenNest.Forms
|
||||
return;
|
||||
|
||||
var nest = editForm.Nest;
|
||||
var views = MdiChildren
|
||||
.OfType<EditNestForm>()
|
||||
.Where(form => ReferenceEquals(form.Nest, nest))
|
||||
.Select(form => form.PlateView)
|
||||
.ToArray();
|
||||
// Clamps feed nesting and validation: never change them under a running operation.
|
||||
if (
|
||||
nestingInProgress
|
||||
|| Application.OpenForms.OfType<NestProgressForm>().Any()
|
||||
|| views.Any(v => v.IsFillInProgress || v.Actions.CurrentAction?.IsBusy() == true)
|
||||
)
|
||||
var views = ViewsOf(nest);
|
||||
if (ClampsInUse(nest) || views.Any(v => v.Actions.CurrentAction?.IsBusy() == true))
|
||||
{
|
||||
MessageBox.Show(
|
||||
this,
|
||||
|
||||
@@ -73,6 +73,19 @@ pre-post verification warns (with per-attempt consent) about parts, cut-offs
|
||||
and direct rapids crossing an active clamp - including the first rapid when a
|
||||
clamp covers the program start point.
|
||||
|
||||
### Removing a clamp by hand
|
||||
|
||||
In Select mode, right-click a clamp on the sheet and choose **Remove clamp-N
|
||||
from This Sheet**. Only that sheet changes. The clamp is drawn dashed with
|
||||
"remove", parts may be placed over it, and it adds the rail's removal minutes to
|
||||
that sheet's cut time, the same as a clamp Auto Nest removed. To put it back,
|
||||
right-click the dashed clamp and choose **Put clamp-N Back on This Sheet**.
|
||||
Parts already over the restored clamp are not moved; Check Overlaps and pre-post
|
||||
verification report them. The menu is not offered while inspecting or
|
||||
previewing a proposal. The change is refused while nesting or a fill is
|
||||
running. **OK** in **Nest > Clamps...** re-resolves every plate from the edited
|
||||
rail and puts every removed clamp back, whether removed by hand or by Auto Nest.
|
||||
|
||||
### Last-resort clamp removal
|
||||
|
||||
When a part cannot be placed on an empty sheet of any offered size with every
|
||||
|
||||
@@ -93,7 +93,7 @@ zones are kept as written.
|
||||
|
||||
The rail's optional `removalMinutes` is the time to take one clamp off and put
|
||||
it back; a missing or invalid value loads as five minutes. A plate on which
|
||||
Auto Nest removed clamps as a last resort lists their rail ids in an optional
|
||||
clamps were removed (by Auto Nest as a last resort, or by hand) lists their rail ids in an optional
|
||||
`removedClamps` array (omitted when none were removed); its `clamps` already
|
||||
exclude them, and it follows the rail minus those ids. A malformed
|
||||
`removedClamps` value reads as none removed (non-string entries are skipped);
|
||||
|
||||
Reference in new issue
Block a user