using System;
using System.Collections.Generic;
using System.Linq;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.Posts.Cincinnati;
///
/// 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.
///
internal static class CincinnatiPlacedOrder
{
/// Why the posted motion sequence differs from the placed one, or null when it equals it.
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 placed;
List<(List 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())
{
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 placed, List<(List 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 placed, IEnumerable 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;
}