Files
OpenNest/OpenNest.Core/Diagnostics/OverlapMaterialCache.cs
T
aj 1b422ef99a fix(diagnostics): read absolute subprogram holes and refuse unknown codes
PlateOverlapAnalyzer converted clean programs directly with
ConvertProgram.ToGeometry, which adds call offsets only to
incremental moves, so holes in absolute-mode subprograms were read at
their frame origin. A square with two absolute holes reported
"Native material contours cross or touch", and the Plan Cutting
overlap gate blocked it while the incremental twin passed. Convert an
owned incremental-mode copy instead (PreparedContours.CopyForGeometry,
now internal, the same normalization the cutting planner uses).

The analyzer also threw on a program whose Codes list was null and
cast or cloned instructions it does not know. It now refuses a
missing list and anything other than the exact built-in instruction
types as an incomplete check, before any copy or conversion runs.
Pre-post verification and the plate overlap overlay share this path.
2026-10-05 21:52:30 -04:00

92 lines
3.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.Diagnostics;
/// <summary>
/// Reuses each clean drawing program's converted entities and prepared (validated, chorded,
/// triangulation-checked) material across overlap requests. Preparation dominates the cost of
/// drawings with many holes, and it depends only on the drawing, not on where parts sit, so a
/// recheck after moving parts only repeats the cheap pose transforms and pair clipping.
/// Entries are keyed by <see cref="Program"/> reference and released with it. A changed code
/// count or program rotation is detected, but that is not a geometry hash: call
/// <see cref="Clear"/> before any in-place edit of a clean program or its hole subprograms.
/// Capture on one thread at a time; prepared entries may be shared by concurrent analyses.
/// </summary>
public sealed class OverlapMaterialCache
{
private readonly ConditionalWeakTable<Program, OverlapSource> sources = new();
public void Clear() => sources.Clear();
internal OverlapSource Get(Program program, Func<Program, OverlapSource> create)
{
if (sources.TryGetValue(program, out var source) && source.Matches(program))
return source;
source = create(program);
sources.AddOrUpdate(program, source);
return source;
}
}
/// <summary>Owned converted entities for one clean program, plus its lazily prepared material.</summary>
internal sealed class OverlapSource
{
private readonly object gate = new();
private readonly int codeCount;
private readonly long rotation;
private PreparedMaterial prepared;
internal OverlapSource(Program program, List<Entity> entities, string error)
{
codeCount = CodeCount(program);
rotation = BitConverter.DoubleToInt64Bits(program.Rotation);
Entities = entities;
Error = error;
}
/// <summary>Never mutated; preparation clones before chaining.</summary>
internal List<Entity> Entities { get; }
internal string Error { get; }
internal bool Matches(Program program) =>
CodeCount(program) == codeCount && BitConverter.DoubleToInt64Bits(program.Rotation) == rotation;
// Program.Codes is a writable field; a missing list is a refused source, not a crash.
private static int CodeCount(Program program) => program.Codes?.Count ?? -1;
/// <summary>
/// Prepares once and shares the result. A geometry failure is cached like a success;
/// cancellation is not, so a superseded request cannot poison the next one.
/// </summary>
internal PreparedMaterial Prepare(CancellationToken cancellationToken)
{
var current = Volatile.Read(ref prepared);
if (current != null)
return current;
lock (gate)
{
if (prepared != null)
return prepared;
PreparedMaterial result;
try
{
result = new PreparedMaterial(OverlapMaterial.Read(Entities, cancellationToken), null);
}
catch (Exception exception) when (PlateOverlapAnalyzer.IsGeometryFailure(exception))
{
result = new PreparedMaterial(null, exception.Message);
}
Volatile.Write(ref prepared, result);
return result;
}
}
}
/// <summary>Validated local-frame material, only ever read (transformed into new polygons).</summary>
internal sealed record PreparedMaterial(OverlapMaterial Material, string Error);