mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 14:35:44 -05:00
N26-295's PT42 pairs saved spacing in slice 1 but every shared edge was still cut twice, costing a pass per edge and risking a freed part tipping under the head on the second pass. Plan Cutting now cuts a plate's common lines once unless the plate is set to cut them twice. Each group is cut member by member, owner before partner (a captured dependency that search and replay enforce, so a kept order cutting the partner first is a constraint conflict): the member's holes, then the shared cuts it owns as open CommonLine centerline cuts entered through the notch on the span's axis, then its remainder pieces, each ramped in from the kerf the shared cut opened. Capture marks the shared spans as tabs on the owned clean copy, so replay's contour accounting proves each perimeter is cut exactly once; a lead that fits at neither end refuses as UnsupportedGeometry naming the span, never shortened. Material still comes from the drawing, so utilization, quantity and overlap results do not change. Members are never support-tabbed; locked or excluded members and broken groups refuse. The CI Fiber post writes shared cuts in the cut-off form with a lead-in and no G41/G42, and remainders in the contour form on their perimeter's compensation side, named by a remainder comment. Cincinnati CL (whose PreApplied mode offsets nothing) and Gravograph refuse such nests, as do the desktop and console for any post without ICommonLinePostProcessor; nothing falls back to a silent double cut. Plate > Arrange > Cut Common Lines Twice keeps slice 1's behavior, persists with the plate and makes an open plan stale. Pre-post review now checks each span is cut once, on its centerline, by the member cut first, and the rapid check treats a finished member's remainders as an obstacle. Compensation start in void and cancel in kerf are unconfirmed on the CI FIBER 4020; release waits on a 3/16 stainless coupon trial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
6.7 KiB
C#
118 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using OpenNest.CNC;
|
|
using OpenNest.CNC.CuttingPlanning;
|
|
using OpenNest.Diagnostics;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Support;
|
|
|
|
namespace OpenNest.Engine.CuttingPlanning;
|
|
|
|
/// <summary>
|
|
/// Capture-time support tabs for one regenerated part (docs/support-tabs.md): with
|
|
/// <see cref="CNC.CuttingStrategy.CuttingParameters.SupportTabs"/> on, a part the support bed
|
|
/// cannot hold level, or by size (no bed, or <see cref="CNC.CuttingStrategy.CuttingParameters.TabBySize"/>)
|
|
/// a part in the size range (<see cref="SupportTabSettings.Analyze"/>), has its perimeter tabbed where the tab steadies
|
|
/// it; a pinned perimeter
|
|
/// keeps its pin (the drawing wins) and only carries the analysis for replay to review. A
|
|
/// supported part, a missing tab configuration or material the analyzer cannot read leave the
|
|
/// preparation as it was.
|
|
/// </summary>
|
|
internal static class SupportTabCapture
|
|
{
|
|
internal static PreparedContours Prepare(PreparedContours prepared, Program ownedClean, Vector location,
|
|
CuttingPlanRequest request, CancellationToken token)
|
|
{
|
|
var parameters = request.ConfirmedParameters;
|
|
// A display perimeter is a supplied blank: it is never cut free, so it needs no tab.
|
|
// A common-line member is never support-tabbed: its outside is cut from its shared spans.
|
|
if (prepared == null || !prepared.HasCutPerimeter || prepared.IsCommonLinePerimeter || parameters?.TabConfig == null
|
|
|| !SupportTabSettings.CanTab(parameters, request.SupportBed))
|
|
return prepared;
|
|
token.ThrowIfCancellationRequested();
|
|
// The clean program is the drawing's own, rotated as placed: its material at the
|
|
// part's location is the material that drops onto the bed. The tab keeps clear of
|
|
// inside corners by twice its size.
|
|
var support = SupportTabSettings.Analyze(parameters, request.SupportBed, request.SupportQuadrant,
|
|
SupportMaterial.FromProgram(ownedClean, location),
|
|
SupportAnalyzer.InsideCornerClearance(parameters.TabConfig.Size));
|
|
if (support is not { IsComplete: true, IsSupported: false } || support.BestTabPoint == null)
|
|
return prepared;
|
|
return prepared.WithSupportTab(support, location, parameters.TabConfig);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A note for a planned support-tabbed part whose tab, at its perimeter entry, sits in an
|
|
/// inside corner because no lead-in fits clear of one. Advisory: it never refuses or
|
|
/// downgrades a plan. A tab that cannot steady its part is <see cref="Review"/>'s warning.
|
|
/// </summary>
|
|
internal static IEnumerable<CuttingPlanFinding> Notes(FixedProgramPlacement planned)
|
|
{
|
|
var prepared = planned.Prepared;
|
|
if (prepared?.Support == null || prepared.IsPinned(prepared.PerimeterOrdinal)
|
|
|| planned.ContourChoices.FirstOrDefault(c => c.ContourOrdinal == prepared.PerimeterOrdinal) is not { } entry
|
|
|| prepared.IsClearOfInsideCorners(entry))
|
|
yield break;
|
|
var tab = entry.Point + planned.Location;
|
|
var at = FormattableString.Invariant($"({tab.X:0.####}, {tab.Y:0.####})");
|
|
yield return JointCuttingPlanSearch.Finding(planned, null,
|
|
$"Its support tab at {at} is in an inside corner, because no lead-in fits clear of one; "
|
|
+ "its nub will be hard to grind flush.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// A <see cref="PostVerificationKind.PartSupport"/> warning when a regenerated part its bed
|
|
/// cannot hold level is cut without a tab that keeps it level: its pin has no tab or a tab
|
|
/// elsewhere, no steadying entry had a lead-in that fits, or no single tab can steady it.
|
|
/// Null otherwise. Like other warnings it makes the proposal UNVERIFIED.
|
|
/// </summary>
|
|
internal static CuttingPlanFinding Review(FixedProgramPlacement source, IReadOnlyList<ContourChoice> choices)
|
|
{
|
|
var prepared = source.Prepared;
|
|
if (prepared?.Support is not { } support || choices == null)
|
|
return null;
|
|
var perimeter = choices.FirstOrDefault(c => c.ContourOrdinal == prepared.PerimeterOrdinal);
|
|
if (perimeter == null)
|
|
return null;
|
|
var tab = perimeter.Point + prepared.SupportOrigin;
|
|
if (prepared.PerimeterTabbed && support.IsSteadiedByTabAt(tab))
|
|
return null;
|
|
var part = source.SourceOrdinal + 1;
|
|
var held = !support.HasBed
|
|
? FormattableString.Invariant($"is {support.Width:0.####} wide, in the width range tabbed by size,")
|
|
: support.IsHeldByTab
|
|
? FormattableString.Invariant($"is {support.Length:0.####} long, short enough for its tab alone to hold,")
|
|
: support.Margin == double.NegativeInfinity
|
|
? "rests on no support contact"
|
|
: support.Margin < 0
|
|
? FormattableString.Invariant($"has its center of gravity {-support.Margin:0.####} outside its support contacts")
|
|
: FormattableString.Invariant($"has its center of gravity only {support.Margin:0.####} inside its support contacts");
|
|
var why = !prepared.PerimeterTabbed
|
|
? $"drawing pin {prepared.PinNumberOf(prepared.PerimeterOrdinal)} cuts its outside without a tab"
|
|
: prepared.IsPinned(prepared.PerimeterOrdinal)
|
|
? $"drawing pin {prepared.PinNumberOf(prepared.PerimeterOrdinal)} puts its tab where it does not steady the part"
|
|
: support.TabCanSupport
|
|
? "no lead-in fits at a point where its tab would steady the part"
|
|
: "no single tab can steady it";
|
|
var needs = support.HasBed && !support.IsHeldByTab ? FormattableString.Invariant($" (needs {support.RequiredMargin:0.####})") : "";
|
|
return new CuttingPlanFinding(source.SourceOrdinal, source.SourcePart, null, null,
|
|
PostVerificationKind.PartSupport, $"Part {part} {held}{needs} and {why}; it may tip once cut free.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// One plan note when support tabs are on but go by size (no support bed, or by size chosen)
|
|
/// and the settings set no size range, so no part was tabbed. Advisory, like <see cref="Notes"/>.
|
|
/// </summary>
|
|
internal static IEnumerable<CuttingPlanFinding> Skipped(CuttingPlanSnapshot snapshot)
|
|
{
|
|
if (snapshot.SupportTabsSkipped)
|
|
yield return new CuttingPlanFinding(null, null, null, null, null,
|
|
snapshot.OwnedParameters?.TabBySize == true
|
|
? "Tabs by size are on, but no width range is set, so no part was tabbed."
|
|
: "Support tabs are on, but the plate has no support bed and no width range is set for "
|
|
+ "plates without one, so no part was tabbed.");
|
|
}
|
|
}
|