mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 23:55: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>
75 lines
3.9 KiB
C#
75 lines
3.9 KiB
C#
using System;
|
|
using System.Linq;
|
|
|
|
namespace OpenNest.CNC
|
|
{
|
|
/// <summary>
|
|
/// How a planned program cuts a common line once (see docs/common-line-cutting.md). A shared cut
|
|
/// is a run of <see cref="LayerType.CommonLine"/> moves: an open centerline cut made without
|
|
/// kerf compensation, with straight leads on its own axis. A remainder (a member's perimeter
|
|
/// between its shared spans) is an open contour whose rapid is followed directly by one of the
|
|
/// <see cref="RemainderMarker(RotationType)"/> comments, naming the winding of the perimeter it
|
|
/// belongs to, so a post compensates it on the perimeter's side even though it encloses nothing.
|
|
/// </summary>
|
|
public static class CommonLineCuts
|
|
{
|
|
/// <summary>The comment after the rapid into a remainder of a clockwise perimeter.</summary>
|
|
public const string ClockwiseRemainder = "COMMON LINE REMAINDER CW";
|
|
|
|
/// <summary>The comment after the rapid into a remainder of a counterclockwise perimeter.</summary>
|
|
public const string CounterclockwiseRemainder = "COMMON LINE REMAINDER CCW";
|
|
|
|
/// <summary>The remainder comment for a perimeter wound <paramref name="winding"/>.</summary>
|
|
public static string RemainderMarker(RotationType winding) =>
|
|
winding == RotationType.CW ? ClockwiseRemainder : CounterclockwiseRemainder;
|
|
|
|
/// <summary>The perimeter winding a remainder comment names, or null for any other code.</summary>
|
|
public static RotationType? RemainderWinding(ICode code) => code switch
|
|
{
|
|
Comment { Value: ClockwiseRemainder } => RotationType.CW,
|
|
Comment { Value: CounterclockwiseRemainder } => RotationType.CCW,
|
|
_ => null,
|
|
};
|
|
|
|
/// <summary>Whether <paramref name="program"/>'s own codes cut a common line once.</summary>
|
|
public static bool IsSinglePass(Program program) =>
|
|
program?.Codes != null && program.Codes.Any(code =>
|
|
code is LinearMove { Layer: LayerType.CommonLine } or ArcMove { Layer: LayerType.CommonLine }
|
|
|| RemainderWinding(code) != null);
|
|
|
|
/// <summary>
|
|
/// Refuses, before any output, a nest with a common line cut once when
|
|
/// <paramref name="postProcessor"/> cannot write shared cuts and remainders: it would cut the
|
|
/// shared cut with compensation (into a part) or a remainder on the wrong side. Posts never
|
|
/// fall back to cutting the line twice; set the plate to cut its common lines twice instead.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The post cannot write the nest.</exception>
|
|
public static void EnsurePostable(Nest nest, IPostProcessor postProcessor)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(nest);
|
|
if (postProcessor is ICommonLinePostProcessor)
|
|
return;
|
|
for (var p = 0; p < nest.Plates.Count; p++)
|
|
{
|
|
var plate = nest.Plates[p];
|
|
for (var i = 0; i < plate.Parts.Count; i++)
|
|
if (IsSinglePass(plate.Parts[i]?.Program))
|
|
throw new InvalidOperationException(
|
|
$"Plate {p + 1}, part {i + 1} cuts a common line once, but the "
|
|
+ $"{postProcessor?.Name ?? "selected"} post cannot write single-pass common lines. "
|
|
+ "Set the plate to cut its common lines twice (Plate > Arrange > Cut Common Lines Twice), "
|
|
+ "plan cutting again, then post.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A post that writes common lines cut once (<see cref="CommonLineCuts"/>): each shared cut
|
|
/// without kerf compensation and each remainder compensated on its perimeter's side. Posts
|
|
/// without it refuse such a nest (<see cref="CommonLineCuts.EnsurePostable"/>).
|
|
/// </summary>
|
|
public interface ICommonLinePostProcessor : IPostProcessor
|
|
{
|
|
}
|
|
}
|