mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 06:51:35 -05:00
The Cincinnati CL post always drew an "incomplete final rapid sequence" warning because it never declared IPostVerificationSupport. It writes etch features before cuts, inserts or drops rapids, and with part sub-programs rapids to each part's corner, so it cannot declare it unconditionally. IPostVerificationSupport gains a nest-aware PlacedProgramOrderChange(nest) (null = preserved; otherwise the reason, shown in the warning). The CL post answers it from its own writer: part sub-programs and smart rapids off, and every part's and hole's posted motions exactly equal to its placed ones. Plan Cutting cuts scribes first, so planned nests qualify. CNC output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.7 KiB
C#
117 lines
4.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Posts.Cincinnati;
|
|
|
|
/// <summary>
|
|
/// Decides whether the post moves through a nest in the placed order the pre-post check
|
|
/// verified (see docs/post-verification.md). The writer keeps plate order, but writes each
|
|
/// part's (and hole's) etch features before its cut features, adds a rapid ahead of a contour
|
|
/// that has none, drops rapids with nothing to cut and, with part sub-programs, rapids to and
|
|
/// from each part's corner. The order is declared preserved only when, for this nest and
|
|
/// these settings, none of that changes a single motion.
|
|
/// </summary>
|
|
internal static class CincinnatiPlacedOrder
|
|
{
|
|
/// <summary>Why the posted motion sequence differs from the placed one, or null when it equals it.</summary>
|
|
internal static string Change(Nest nest, CincinnatiPostConfig config)
|
|
{
|
|
if (config.UsePartSubprograms)
|
|
return "calls part sub-programs, which add rapids to and from each part's corner";
|
|
if (config.UseSmartRapids)
|
|
return "uses smart rapids (G121), whose blended rapids leave the straight line";
|
|
if (nest?.Plates == null)
|
|
return null;
|
|
|
|
for (var plateIndex = 0; plateIndex < nest.Plates.Count; plateIndex++)
|
|
{
|
|
var parts = nest.Plates[plateIndex]?.Parts;
|
|
if (parts == null)
|
|
continue;
|
|
for (var partIndex = 0; partIndex < parts.Count; partIndex++)
|
|
{
|
|
var part = parts[partIndex];
|
|
if (part?.Program == null)
|
|
continue;
|
|
var where = $"part {partIndex + 1} on plate {plateIndex + 1}";
|
|
List<ICode> placed;
|
|
List<(List<ICode> codes, bool isEtch)> posted;
|
|
try
|
|
{
|
|
placed = FeatureUtils.AbsoluteCodes(FeatureUtils.PostedProgram(part));
|
|
posted = FeatureUtils.SplitAndClassify(part);
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
return $"cannot post {where} as placed";
|
|
}
|
|
|
|
var change = ChangeTo(placed, posted, where);
|
|
if (change != null)
|
|
return change;
|
|
|
|
foreach (var call in placed.OfType<SubProgramCall>())
|
|
{
|
|
if (call.Program == null || !FeatureUtils.HasCut(call.Program))
|
|
continue;
|
|
var hole = CincinnatiPartSubprogramWriter.PostedHole(call.Program);
|
|
change = ChangeTo(
|
|
FeatureUtils.AbsoluteCodes(call.Program),
|
|
FeatureUtils.ClassifyAndOrder(FeatureUtils.SplitByRapids(hole.Codes)),
|
|
$"a hole of {where}"
|
|
);
|
|
if (change != null)
|
|
return change;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static string ChangeTo(List<ICode> placed, List<(List<ICode> codes, bool isEtch)> posted, string where)
|
|
{
|
|
if (SameMotions(placed, posted.SelectMany(feature => feature.codes)))
|
|
return null;
|
|
var cutSeen = false;
|
|
foreach (var feature in FeatureUtils.SplitByRapids(placed))
|
|
{
|
|
var isEtch = FeatureUtils.IsEtch(feature);
|
|
if (isEtch && cutSeen)
|
|
return $"writes the etch features of {where} before the cuts placed ahead of them";
|
|
cutSeen |= !isEtch;
|
|
}
|
|
return $"adds or drops rapids in {where}";
|
|
}
|
|
|
|
// Motions and hole calls in the same order with exactly the same coordinates.
|
|
private static bool SameMotions(IEnumerable<ICode> placed, IEnumerable<ICode> posted)
|
|
{
|
|
var a = placed.Where(code => code is Motion or SubProgramCall).ToList();
|
|
var b = posted.Where(code => code is Motion or SubProgramCall).ToList();
|
|
if (a.Count != b.Count)
|
|
return false;
|
|
for (var i = 0; i < a.Count; i++)
|
|
{
|
|
if (!Same(a[i], b[i]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool Same(ICode a, ICode b) =>
|
|
(a, b) switch
|
|
{
|
|
(SubProgramCall x, SubProgramCall y) => ReferenceEquals(x.Program, y.Program) && Exact(x.Offset, y.Offset),
|
|
(ArcMove x, ArcMove y) => Exact(x.EndPoint, y.EndPoint)
|
|
&& Exact(x.CenterPoint, y.CenterPoint)
|
|
&& x.Rotation == y.Rotation,
|
|
(Motion x, Motion y) => x.GetType() == y.GetType() && Exact(x.EndPoint, y.EndPoint),
|
|
_ => false,
|
|
};
|
|
|
|
private static bool Exact(Vector a, Vector b) => a.X == b.X && a.Y == b.Y;
|
|
}
|