fix(cutting): validate original graphs before capture clones

This commit is contained in:
aj committed 2026-10-04 21:56:49 -04:00
1 parent d7204e8d07
commit e00aa113a0
2 files changed
+139 -8

No files matched your search

@@ -46,19 +46,21 @@ public static class CuttingPlanService
throw new ArgumentException("Missing/duplicate source placement or invalid pose.");
if (source.BaseDrawing.IsCutOff)
throw new NotSupportedException("Cutoff dependency ordering is outside the fixed-program route slice.");
var clean = ExecutionMotionReader.Read(source.BaseDrawing.Program, Vector.Zero, null, token);
// Validate both original graphs before Clone or any virtual transform can
// erase unsupported runtime semantics, including fixed/ineligible targets.
ValidateCloneGraph(source.BaseDrawing.Program, token);
ValidateCloneGraph(source.Program, token);
var clean = ExecutionMotionReader.ReadSupported(source.BaseDrawing.Program, Vector.Zero, null, token);
if (!clean.HasCuttingContour)
throw new NotSupportedException("Scribe-only or noncutting source drawings are outside this route slice.");
var execution = ExecutionMotionReader.Read(source.Program, source.Location, request.StartPoint, token);
var execution = ExecutionMotionReader.ReadSupported(source.Program, source.Location, request.StartPoint, token);
if (!execution.HasCuttingContour)
throw new ArgumentException("Placed program has no nonzero cutting contour motions.");
ValidateCloneGraph(source.Program, token);
var ownedProgram = (Program)source.Program.Clone();
PreparedContours prepared = null;
LeadMaterialSnapshot material = null;
if (request.ConfirmedParameters != null)
{
ValidateCloneGraph(source.BaseDrawing.Program, token);
var ownedClean = (Program)source.BaseDrawing.Program.Clone();
ownedClean.Rotate(source.Rotation - source.BaseDrawing.Program.Rotation);
material = LeadMaterialSnapshot.Capture(ownedClean, source.Location, token);
@@ -274,6 +276,7 @@ public static class CuttingPlanService
{
var active = new HashSet<Program>(ReferenceEqualityComparer.Instance);
var done = new HashSet<Program>(ReferenceEqualityComparer.Instance);
var budget = 1000000;
Visit(program);
void Visit(Program current)
{
@@ -281,13 +284,34 @@ public static class CuttingPlanService
if (current == null || active.Contains(current) || active.Count >= 64)
throw new ArgumentException("Missing, recursive or excessively nested clone graph.");
if (done.Contains(current)) return;
// The reader already validated the executed graph. Inactive registered
// subprograms may be motionless; preserve them exactly, but guard Clone traversal.
if (current.Codes == null || current.Codes.Any(c => c == null))
// Inactive registered subprograms may be motionless, but Clone still
// traverses them. Refuse unknown semantics without invoking virtual Clone.
if (current.GetType() != typeof(Program) || !Enum.IsDefined(current.Mode))
throw new NotSupportedException("Unsupported program runtime type or mode.");
if (current.Codes == null)
throw new ArgumentException("Missing clone graph instructions.");
active.Add(current);
foreach (var child in current.SubPrograms.Values.Concat(current.Codes.OfType<SubProgramCall>().Select(c => c.Program)))
foreach (var code in current.Codes)
{
token.ThrowIfCancellationRequested();
if (--budget < 0)
throw new ArgumentException("Clone graph exceeds the verification limit.");
if (code == null)
throw new ArgumentException("Missing clone graph instruction.");
var type = code.GetType();
if (type != typeof(RapidMove) && type != typeof(LinearMove) && type != typeof(ArcMove)
&& type != typeof(SubProgramCall) && type != typeof(Comment) && type != typeof(Feedrate) && type != typeof(Kerf))
throw new NotSupportedException("Unsupported instruction runtime type.");
if (code is SubProgramCall call)
Visit(call.Program);
}
foreach (var child in current.SubPrograms.Values)
{
token.ThrowIfCancellationRequested();
if (--budget < 0)
throw new ArgumentException("Clone graph exceeds the verification limit.");
Visit(child);
}
active.Remove(current);
done.Add(current);
}