fix(ui): cache offset paths and scale temp parts on zoom

Cache offset polygon geometry on LayoutPart so DrawOffset no longer
recomputes the expensive offset pipeline every paint cycle. The costly
OffsetEntity/ToPolygonWithTolerance/RemoveSelfIntersections chain now
runs only when rotation, spacing, or tolerance actually changes.

Also update temporary parts in UpdateMatrix() so preview parts during
nesting scale correctly with zoom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 09:29:27 -04:00
parent 501b2553ad
commit 776e9d218c
2 changed files with 99 additions and 29 deletions

View File

@@ -12,7 +12,6 @@ using System.Windows.Forms;
using OpenNest.Actions;
using OpenNest.CNC;
using OpenNest.Collections;
using OpenNest.Converters;
using OpenNest.Forms;
using OpenNest.Geometry;
using OpenNest.Math;
@@ -512,37 +511,17 @@ namespace OpenNest.Controls
{
using (var offsetPen = new Pen(Color.FromArgb(120, 255, 100, 100)))
{
for (int i = 0; i < parts.Count; i++)
for (var i = 0; i < parts.Count; i++)
{
var part = parts[i].BasePart;
var entities = ConvertProgram.ToGeometry(part.Program);
var shapes = Helper.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
var layoutPart = parts[i];
foreach (var shape in shapes)
{
var offsetEntity = shape.OffsetEntity(Plate.PartSpacing, OffsetSide.Left) as Shape;
if (layoutPart.IsDirty)
layoutPart.Update(this);
if (offsetEntity == null)
continue;
layoutPart.UpdateOffset(Plate.PartSpacing, OffsetTolerance, Matrix);
var polygon = offsetEntity.ToPolygonWithTolerance(OffsetTolerance);
polygon.RemoveSelfIntersections();
polygon.Offset(part.Location);
if (polygon.Vertices.Count < 2)
continue;
var pts = new PointF[polygon.Vertices.Count];
for (int j = 0; j < pts.Length; j++)
pts[j] = new PointF((float)polygon.Vertices[j].X, (float)polygon.Vertices[j].Y);
var path = new GraphicsPath();
path.AddLines(pts);
path.Transform(Matrix);
g.DrawPath(offsetPen, path);
path.Dispose();
}
if (layoutPart.OffsetPath != null)
g.DrawPath(offsetPen, layoutPart.OffsetPath);
}
}
}
@@ -1090,6 +1069,7 @@ namespace OpenNest.Controls
{
base.UpdateMatrix();
parts.ForEach(p => p.Update(this));
temporaryParts.ForEach(p => p.Update(this));
}
}
}

View File

@@ -1,7 +1,10 @@
using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
using OpenNest.Controls;
using OpenNest.Converters;
using OpenNest.Geometry;
namespace OpenNest
@@ -17,6 +20,11 @@ namespace OpenNest
private Brush brush;
private Pen pen;
private List<PointF[]> _offsetPolygonPoints;
private double _cachedOffsetSpacing;
private double _cachedOffsetTolerance;
private double _cachedOffsetRotation = double.NaN;
public readonly Part BasePart;
static LayoutPart()
@@ -92,6 +100,8 @@ namespace OpenNest
g.DrawString(id, programIdFont, Brushes.Black, pt.X, pt.Y);
}
public GraphicsPath OffsetPath { get; private set; }
public void Update(DrawControl plateView)
{
Path = GraphicsHelper.GetGraphicsPath(BasePart.Program, BasePart.Location);
@@ -99,6 +109,86 @@ namespace OpenNest
IsDirty = false;
}
public void UpdateOffset(double spacing, double tolerance, Matrix matrix)
{
if (_offsetPolygonPoints == null ||
spacing != _cachedOffsetSpacing ||
tolerance != _cachedOffsetTolerance ||
BasePart.Rotation != _cachedOffsetRotation)
{
_offsetPolygonPoints = ComputeOffsetPolygons(spacing, tolerance);
_cachedOffsetSpacing = spacing;
_cachedOffsetTolerance = tolerance;
_cachedOffsetRotation = BasePart.Rotation;
}
RebuildOffsetPath(matrix);
}
public void InvalidateOffset()
{
_offsetPolygonPoints = null;
}
private List<PointF[]> ComputeOffsetPolygons(double spacing, double tolerance)
{
var result = new List<PointF[]>();
var entities = ConvertProgram.ToGeometry(BasePart.Program);
var shapes = Helper.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
foreach (var shape in shapes)
{
var offsetEntity = shape.OffsetEntity(spacing, OffsetSide.Left) as Shape;
if (offsetEntity == null)
continue;
var polygon = offsetEntity.ToPolygonWithTolerance(tolerance);
polygon.RemoveSelfIntersections();
if (polygon.Vertices.Count < 2)
continue;
var pts = new PointF[polygon.Vertices.Count];
for (var j = 0; j < pts.Length; j++)
pts[j] = new PointF((float)polygon.Vertices[j].X, (float)polygon.Vertices[j].Y);
result.Add(pts);
}
return result;
}
private void RebuildOffsetPath(Matrix matrix)
{
OffsetPath?.Dispose();
if (_offsetPolygonPoints == null || _offsetPolygonPoints.Count == 0)
{
OffsetPath = null;
return;
}
var path = new GraphicsPath();
var dx = (float)BasePart.Location.X;
var dy = (float)BasePart.Location.Y;
foreach (var pts in _offsetPolygonPoints)
{
var offsetPts = new PointF[pts.Length];
for (var i = 0; i < pts.Length; i++)
offsetPts[i] = new PointF(pts[i].X + dx, pts[i].Y + dy);
path.AddLines(offsetPts);
path.StartFigure();
}
path.Transform(matrix);
OffsetPath = path;
}
public static LayoutPart Create(Part part, PlateView plateView)
{
var layoutPart = new LayoutPart(part);