mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-06 15:12:20 -04:00
fix(diagnostics): refuse absolute subprograms instead of normalizing
1b422efread absolute-mode hole subprograms by converting an incremental-mode copy of every clean program. Rebuilding absolute endpoints from incremental deltas is not exact: after a rapid at 1e12 a 1x10 rectangle moved by about 2.4e-5 and a real 2e-5 overlap was reported clear, in the overlap overlay and pre-post verification as well as Plan Cutting. Convert programs directly again, which reads absolute coordinates exactly, and refuse an absolute-mode subprogram as an incomplete check instead: the converter adds a call's frame offset to incremental moves only, so it would read such a hole at its frame origin. OpenNest writes hole subprograms in incremental mode. The null-list and unknown-instruction refusals from1b422efstay, and CopyForGeometry is private to the planner again.
This commit is contained in:
1 parent
65d320d2e5
commit
48367da820
6 files changed
+62
-22
No files matched your search
@@ -215,12 +215,7 @@ public sealed class PreparedContours
|
||||
private static Vector Start(Entity entity) => entity is Line line ? line.StartPoint : ((Arc)entity).StartPoint();
|
||||
private static Vector End(Entity entity) => entity is Line line ? line.EndPoint : ((Arc)entity).EndPoint();
|
||||
|
||||
/// <summary>
|
||||
/// Owned copy of a validated graph with every program in incremental mode, so
|
||||
/// <see cref="ConvertProgram.ToGeometry"/> keeps absolute subprogram frame offsets. Shared
|
||||
/// subprograms stay shared. Validate exact instruction types first: this clones every code.
|
||||
/// </summary>
|
||||
internal static Program CopyForGeometry(Program source, CancellationToken token)
|
||||
private static Program CopyForGeometry(Program source, CancellationToken token)
|
||||
{
|
||||
var copies = new Dictionary<Program, Program>(ReferenceEqualityComparer.Instance);
|
||||
return Copy(source);
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC.CuttingPlanning;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
@@ -72,12 +71,10 @@ public static class PlateOverlapAnalyzer
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateProgram(program, new HashSet<Program>(ReferenceEqualityComparer.Instance));
|
||||
// Convert an incremental-mode copy: the converter adds call offsets to incremental moves
|
||||
// only, so absolute subprogram holes would otherwise land at their frame origin. The
|
||||
// copy is owned; the live program is neither converted in place nor rotated.
|
||||
var geometry = PreparedContours.CopyForGeometry(program, CancellationToken.None);
|
||||
return new OverlapSource(program, ConvertProgram.ToGeometry(geometry)
|
||||
ValidateProgram(program, new HashSet<Program>(ReferenceEqualityComparer.Instance), false);
|
||||
// Conversion creates fresh geometry, including expanded shared hole calls;
|
||||
// no cloning/rotation of a live program or subprogram is necessary.
|
||||
return new OverlapSource(program, ConvertProgram.ToGeometry(program)
|
||||
.Where(entity => SpecialLayers.IsMaterial(entity.Layer)
|
||||
&& entity.Layer != SpecialLayers.Leadin
|
||||
&& entity.Layer != SpecialLayers.Leadout).ToList(), null);
|
||||
@@ -278,12 +275,18 @@ public static class PlateOverlapAnalyzer
|
||||
typeof(Comment), typeof(Feedrate), typeof(Kerf),
|
||||
];
|
||||
|
||||
private static void ValidateProgram(Program program, HashSet<Program> visiting)
|
||||
private static void ValidateProgram(Program program, HashSet<Program> visiting, bool subprogram)
|
||||
{
|
||||
if (program == null || !visiting.Add(program) || visiting.Count > 64)
|
||||
throw new ArgumentException("Missing, recursive, or excessively nested subprogram.");
|
||||
if (program.Codes == null)
|
||||
throw new ArgumentException("Program has no instruction list.");
|
||||
// The converter adds a call's frame offset to incremental moves only, so an absolute
|
||||
// subprogram would be read at its frame origin. Converting it exactly needs a lossless
|
||||
// frame transform; until then it is refused rather than misread (OpenNest writes hole
|
||||
// subprograms in incremental mode).
|
||||
if (subprogram && program.Mode == Mode.Absolute)
|
||||
throw new NotSupportedException("Absolute-mode subprograms are not supported by the overlap check.");
|
||||
foreach (var code in program.Codes)
|
||||
{
|
||||
if (code == null)
|
||||
@@ -297,7 +300,7 @@ public static class PlateOverlapAnalyzer
|
||||
{
|
||||
if (!OverlapMaterial.IsFinite(call.Offset) || !double.IsFinite(call.Rotation))
|
||||
throw new ArgumentException("Subprogram pose must be finite.");
|
||||
ValidateProgram(call.Program, visiting);
|
||||
ValidateProgram(call.Program, visiting, true);
|
||||
}
|
||||
}
|
||||
visiting.Remove(program);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class CuttingPlanBatchTests
|
||||
[Theory]
|
||||
[InlineData(Mode.Absolute)]
|
||||
[InlineData(Mode.Incremental)]
|
||||
public void Plan_SubprogramHolesInEitherMode_AreNotFalseOverlaps(Mode mode)
|
||||
public void Plan_SubprogramHoles_AreNeverAFalseOverlap(Mode mode)
|
||||
{
|
||||
var hole = new Program();
|
||||
hole.MoveTo(1, 0);
|
||||
@@ -115,8 +115,17 @@ public class CuttingPlanBatchTests
|
||||
var proposal = CuttingPlanBatch.Capture([plate], ExplicitContourTests.Parameters(), false).Plan();
|
||||
|
||||
var planned = Assert.Single(proposal.Plates);
|
||||
Assert.True(planned.IsOverlapClear, string.Join("; ", planned.Overlap.Issues.Select(i => i.Message)));
|
||||
Assert.True(planned.IsReady);
|
||||
Assert.True(planned.IsRouteReady);
|
||||
Assert.Empty(planned.Overlap.Pairs);
|
||||
if (mode == Mode.Incremental)
|
||||
Assert.True(planned.IsReady, string.Join("; ", planned.Overlap.Issues.Select(i => i.Message)));
|
||||
else
|
||||
{
|
||||
// The overlap check cannot read absolute subprograms yet: blocked as unchecked, not as overlapping.
|
||||
Assert.False(planned.IsReady);
|
||||
Assert.Contains("Overlap check incomplete for part 1: Absolute-mode subprograms",
|
||||
string.Join("\n", proposal.Describe("in")));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -504,7 +504,7 @@ public class PlateOverlapAnalyzerTests
|
||||
[Theory]
|
||||
[InlineData(Mode.Absolute)]
|
||||
[InlineData(Mode.Incremental)]
|
||||
public void Analyze_SubprogramHolesKeepTheirCallOffsetsInEitherMode(Mode mode)
|
||||
public void Analyze_IncrementalHolesKeepTheirOffsetsAndAbsoluteOnesAreRefused(Mode mode)
|
||||
{
|
||||
var hole = new Program();
|
||||
hole.MoveTo(1, 0);
|
||||
@@ -525,8 +525,37 @@ public class PlateOverlapAnalyzerTests
|
||||
|
||||
var report = PlateOverlapAnalyzer.Analyze(new[] { host, insert });
|
||||
|
||||
Assert.True(report.IsComplete, string.Join("; ", report.Issues.Select(issue => issue.Message)));
|
||||
// Never a false overlap: incremental holes are read at their call offsets, and an absolute
|
||||
// subprogram, which the converter would read at its frame origin, is an incomplete check.
|
||||
Assert.Empty(report.Pairs);
|
||||
if (mode == Mode.Incremental)
|
||||
Assert.True(report.IsComplete, string.Join("; ", report.Issues.Select(issue => issue.Message)));
|
||||
else
|
||||
{
|
||||
var issue = Assert.Single(report.Issues);
|
||||
Assert.Equal((0, (int?)null), (issue.PartAId, issue.PartBId));
|
||||
Assert.Contains("Absolute-mode subprograms", issue.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Analyze_AbsoluteCoordinatesFarFromTheMaterialAreReadExactly()
|
||||
{
|
||||
// Rebuilding absolute endpoints from incremental deltas after a rapid at 1e12 moves the
|
||||
// contour by about 2.4e-5, enough to hide this 2e-5 wide overlap.
|
||||
var far = new Program(Mode.Absolute);
|
||||
far.Codes.Add(new RapidMove(new Vector(1e12, 0)));
|
||||
far.Codes.Add(new RapidMove(new Vector(0.1, 0)));
|
||||
foreach (var point in new[] { new Vector(1.1, 0), new Vector(1.1, 10), new Vector(0.1, 10), new Vector(0.1, 0) })
|
||||
far.Codes.Add(new LinearMove(point));
|
||||
var first = new Part(new Drawing("far rapid", far));
|
||||
var second = Rectangle(1.09998, 0, 1, 10);
|
||||
|
||||
var report = PlateOverlapAnalyzer.Analyze(new[] { first, second });
|
||||
|
||||
Assert.True(report.IsComplete, string.Join("; ", report.Issues.Select(issue => issue.Message)));
|
||||
var pair = Assert.Single(report.Pairs);
|
||||
Assert.Equal(2e-4, pair.Area, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -202,7 +202,8 @@ plans every plate that has parts. Both open one dialog built on
|
||||
already pass the checks.
|
||||
- Every plate is captured on the UI thread and checked and planned on a worker. Clean part
|
||||
material is checked for overlaps with the pre-post overlap analyzer; overlapping parts or
|
||||
an incomplete check block that plate whatever its route. A free-order search that ends
|
||||
an incomplete check (see [pre-post verification](post-verification.md)) block that plate
|
||||
whatever its route. A free-order search that ends
|
||||
`NoSolutionWithinBudget` is retried once with the current part order, and the summary
|
||||
says the order was kept. A kept order is allowed 400 expansions per part (at
|
||||
least the default 20000), because it still searches contour order and entries.
|
||||
|
||||
@@ -17,7 +17,10 @@ shows all three check categories, even when there are no findings:
|
||||
|
||||
Findings identify the plate and part's position in the cutting sequence. Read the
|
||||
scrollable report, cancel to fix the nest, then post again to rerun verification.
|
||||
Uncheckable geometry is reported as incomplete, not passed. Multiple interrupted
|
||||
Uncheckable geometry is reported as incomplete, not passed. The overlap check reads
|
||||
clean drawing programs made of the built-in instructions only; a missing instruction
|
||||
list, any other instruction type, or a hole stored as an absolute-mode subprogram
|
||||
(OpenNest writes them incremental) makes that part's check incomplete. Multiple interrupted
|
||||
cut fragments, or a lead-out after an open contour that might cut through a tab,
|
||||
also require manual review rather than being assumed retained.
|
||||
|
||||
|
||||
Reference in new issue
Block a user