Files
OpenNest/OpenNest.Engine/Jobs/Cutouts/CutoutMemo.cs
T
ajandClaude Opus 5.5 dd2c00aed4 perf(engine): replay cutout pre-pass clearance checks and trim hole subtraction
Profiling the cutout pre-pass on real round-part jobs put ~97% of its time in
the frame-wall NestLayoutCheck.Clears inside CutoutCirclePacker.Fill. Ear-clipped
round frames make every perimeter/insert triangle pair overlap, so a clear pair
runs hole subtraction for every pair. The same question repeats for every frame
angle and repeated frame instance, because Automatic inserts get identical
frame-local poses.

- CutoutMemo, one per Prepare, replays geometry reads, circle items and Clears
  answers. Keys hold geometry by reference, pose bit patterns, argument order
  and spacing. It is threaded through CutoutCirclePacker.Fill and the router's
  round-only path.
- TriangulatedRegion.SubtractAllHoles measures each piece's bounds once, skips
  holes and hole triangles that no piece reaches, and ping-pongs the remainder
  between scratch buffers instead of copying it into pooled ones. Clip
  arithmetic and order are unchanged.

Bundles are byte-identical on 14 real and 10 synthetic ring jobs. Pre-pass time
over the real set drops from 99.6 s to 10.1 s (N0414-02 35.8 s -> 0.6 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:36:02 -04:00

60 lines
2.3 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using OpenNest.Engine.CirclePacking;
namespace OpenNest.Engine.Jobs.Cutouts;
/// <summary>Exact replays of pure cutout computations within one pre-pass, which asks the same
/// questions for every frame instance and frame angle. A clear frame and insert cost
/// <see cref="NestLayoutCheck.Clears"/> its worst case: every perimeter triangle pair, then every
/// hole triangle. Not thread-safe; one instance per call.</summary>
/// <remarks>Clearance keys hold both geometries by reference, their poses bit for bit, the
/// argument order and the spacing, so a replay is the answer the check would give. Geometry is
/// read once per snapshot; shared readers must clone before transforming, as they already do.</remarks>
internal sealed class CutoutMemo
{
private readonly record struct Key(JobPartGeometry A, long AX, long AY, long ARotation,
JobPartGeometry B, long BX, long BY, long BRotation, long Spacing);
private readonly Dictionary<Key, bool> clearances = new();
private readonly Dictionary<PartGeometrySnapshot, JobPartGeometry?> geometries =
new(ReferenceEqualityComparer.Instance);
private readonly Dictionary<JobPartGeometry, Item> items = new();
internal JobPartGeometry? Read(PartGeometrySnapshot snapshot)
{
if (!geometries.TryGetValue(snapshot, out var geometry))
{
geometry = JobPartGeometry.TryRead(snapshot);
geometries.Add(snapshot, geometry);
}
return geometry;
}
internal Item ItemOf(JobPartGeometry geometry)
{
if (!items.TryGetValue(geometry, out var item))
{
item = Item.Create(geometry);
items.Add(geometry, item);
}
return item;
}
internal bool Clears(JobPartGeometry a, NestJobPlacement pa,
JobPartGeometry b, NestJobPlacement pb, double spacing)
{
var key = new Key(a, Bits(pa.X), Bits(pa.Y), Bits(pa.Rotation),
b, Bits(pb.X), Bits(pb.Y), Bits(pb.Rotation), Bits(spacing));
if (!clearances.TryGetValue(key, out var clears))
{
clears = NestLayoutCheck.Clears(a, pa, b, pb, spacing);
clearances.Add(key, clears);
}
return clears;
}
private static long Bits(double value) => BitConverter.DoubleToInt64Bits(value);
}