2 Commits

Author SHA1 Message Date
aj 53988acefc fix(io): deduplicate circles and full-circle arcs during DXF import
Duplicate circle entities at the same location inflated pierce counts
and cut pricing (e.g. SULLYS-035 showed 9 pierces instead of 8).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 13:21:03 -04:00
aj a8d90be2ea feat: add layer filter overloads to Dxf.GetGeometry()
Add optional Func<string, bool> layerFilter parameter to ConvertEntities
and two new GetGeometry overloads (path and stream) that accept a layer
filter. This lets callers control which layers to exclude instead of
being limited to the hardcoded IsNonCutLayer check. Existing overloads
without the filter continue to use the default IsNonCutLayer behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 13:21:02 -04:00
11 changed files with 52 additions and 663 deletions
-5
View File
@@ -16,11 +16,6 @@ namespace OpenNest.IO
/// </summary>
public bool DetectBends { get; set; } = true;
/// <summary>
/// When true, detects and identifies title block entities during import. Default true.
/// </summary>
public bool DetectTitleBlock { get; set; } = true;
/// <summary>
/// Override the drawing name. Null = filename without extension.
/// </summary>
-5
View File
@@ -45,10 +45,5 @@ namespace OpenNest.IO
/// that need access to non-geometry entities (e.g., text annotations).
/// </summary>
public CadDocument Document { get; set; }
/// <summary>
/// GUIDs of entities identified as part of the title block during import.
/// </summary>
public HashSet<System.Guid> TitleBlockEntityIds { get; set; }
}
}
-22
View File
@@ -27,7 +27,6 @@ namespace OpenNest.IO
var dxf = Dxf.Import(path);
RemoveDuplicateArcs(dxf.Entities);
RemoveZeroSweepArcs(dxf.Entities);
var bends = new List<Bend>();
if (options.DetectBends && dxf.Document != null)
@@ -41,10 +40,6 @@ namespace OpenNest.IO
Bend.UpdateEtchEntities(dxf.Entities, bends);
HashSet<System.Guid> titleBlockIds = null;
if (options.DetectTitleBlock)
titleBlockIds = TitleBlockDetector.Detect(dxf.Entities, dxf.Document);
return new CadImportResult
{
Entities = dxf.Entities,
@@ -53,7 +48,6 @@ namespace OpenNest.IO
SourcePath = path,
Name = options.Name ?? Path.GetFileNameWithoutExtension(path),
Document = dxf.Document,
TitleBlockEntityIds = titleBlockIds,
};
}
@@ -144,25 +138,9 @@ namespace OpenNest.IO
.Where(e => !(e.Layer != null && e.Layer.IsVisible && e.IsVisible))
.Select(e => e.Id));
if (result.TitleBlockEntityIds != null)
{
var sourceIds = new HashSet<System.Guid>(drawing.SourceEntities.Select(e => e.Id));
foreach (var id in result.TitleBlockEntityIds)
{
if (sourceIds.Contains(id))
drawing.SuppressedEntityIds.Add(id);
}
}
return drawing;
}
internal static void RemoveZeroSweepArcs(List<Entity> entities)
{
entities.RemoveAll(e =>
e is Arc arc && arc.StartAngle.IsEqualTo(arc.EndAngle, Tolerance.ChainTolerance));
}
internal static void RemoveDuplicateArcs(List<Entity> entities)
{
var circles = entities.OfType<Circle>().ToList();
+33 -2
View File
@@ -65,6 +65,36 @@ namespace OpenNest.IO
}
}
public static List<Entity> GetGeometry(string path, Func<string, bool> layerFilter)
{
try
{
using var reader = new DxfReader(path);
var doc = reader.Read();
return ConvertEntities(doc, layerFilter);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return new List<Entity>();
}
}
public static List<Entity> GetGeometry(Stream stream, Func<string, bool> layerFilter)
{
try
{
using var reader = new DxfReader(stream);
var doc = reader.Read();
return ConvertEntities(doc, layerFilter);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return new List<Entity>();
}
}
#endregion
#region Export
@@ -128,16 +158,17 @@ namespace OpenNest.IO
}
}
private static List<Entity> ConvertEntities(CadDocument doc)
private static List<Entity> ConvertEntities(CadDocument doc, Func<string, bool> layerFilter = null)
{
var entities = new List<Entity>();
var lines = new List<Line>();
var arcs = new List<Arc>();
var circles = new List<Circle>();
var filter = layerFilter ?? IsNonCutLayer;
foreach (var entity in doc.Entities)
{
if (IsNonCutLayer(entity.Layer?.Name))
if (filter(entity.Layer?.Name))
continue;
switch (entity)
-312
View File
@@ -1,312 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ACadSharp;
using OpenNest.Geometry;
namespace OpenNest.IO
{
public static class TitleBlockDetector
{
private static readonly HashSet<string> TitleBlockLayerNames = new(StringComparer.OrdinalIgnoreCase)
{
"TITLE", "TITLEBLOCK", "TITLE_BLOCK", "BORDER", "FRAME",
"TB", "INFO", "SHEET", "ANNOTATION"
};
public static HashSet<Guid> Detect(List<Entity> entities, CadDocument document)
{
var flagged = new HashSet<Guid>();
DetectByLayerName(entities, flagged);
DetectBorder(entities, flagged);
if (document != null)
DetectTitleBlockRegion(entities, document, flagged);
return flagged;
}
private static void DetectByLayerName(List<Entity> entities, HashSet<Guid> flagged)
{
foreach (var entity in entities)
{
if (entity.Layer?.Name != null && TitleBlockLayerNames.Contains(entity.Layer.Name))
flagged.Add(entity.Id);
}
}
private static void DetectBorder(List<Entity> entities, HashSet<Guid> flagged)
{
var lines = entities.OfType<Line>().Where(l => !flagged.Contains(l.Id)).ToList();
if (lines.Count < 4) return;
var bounds = entities.GetBoundingBox();
if (bounds == null || bounds.Area() < OpenNest.Math.Tolerance.Epsilon) return;
var borderCount = 0;
foreach (var line in lines)
{
if (IsBorderLine(line, bounds))
{
flagged.Add(line.Id);
borderCount++;
}
}
if (borderCount >= 2)
DetectZoneMarkers(lines, bounds, flagged);
}
private static bool IsBorderLine(Line line, Box bounds)
{
var dx = line.EndPoint.X - line.StartPoint.X;
var dy = line.EndPoint.Y - line.StartPoint.Y;
var length = System.Math.Sqrt(dx * dx + dy * dy);
var angleRad = System.Math.Atan2(System.Math.Abs(dy), System.Math.Abs(dx));
var angularTolerance = OpenNest.Math.Angle.ToRadians(2.0);
var positionTolerance = System.Math.Max(bounds.Length, bounds.Width) * 0.01;
var isHorizontal = angleRad < angularTolerance;
var isVertical = System.Math.Abs(angleRad - System.Math.PI / 2) < angularTolerance;
if (!isHorizontal && !isVertical) return false;
var minSpan = isHorizontal ? bounds.Length * 0.8 : bounds.Width * 0.8;
if (length < minSpan) return false;
if (isHorizontal)
{
var midY = (line.StartPoint.Y + line.EndPoint.Y) / 2;
return System.Math.Abs(midY - bounds.Bottom) < positionTolerance
|| System.Math.Abs(midY - bounds.Top) < positionTolerance;
}
else
{
var midX = (line.StartPoint.X + line.EndPoint.X) / 2;
return System.Math.Abs(midX - bounds.Left) < positionTolerance
|| System.Math.Abs(midX - bounds.Right) < positionTolerance;
}
}
private static void DetectZoneMarkers(List<Line> lines, Box bounds, HashSet<Guid> flagged)
{
var positionTolerance = System.Math.Max(bounds.Length, bounds.Width) * 0.01;
var maxTickLength = System.Math.Max(bounds.Length, bounds.Width) * 0.05;
var angularTolerance = OpenNest.Math.Angle.ToRadians(2.0);
foreach (var line in lines)
{
if (flagged.Contains(line.Id)) continue;
var dx = line.EndPoint.X - line.StartPoint.X;
var dy = line.EndPoint.Y - line.StartPoint.Y;
var length = System.Math.Sqrt(dx * dx + dy * dy);
if (length > maxTickLength || length < OpenNest.Math.Tolerance.Epsilon) continue;
var angleRad = System.Math.Atan2(System.Math.Abs(dy), System.Math.Abs(dx));
var isVertical = System.Math.Abs(angleRad - System.Math.PI / 2) < angularTolerance;
var isHorizontal = angleRad < angularTolerance;
if (!isVertical && !isHorizontal) continue;
var touchesEdge = false;
if (isVertical)
{
var minY = System.Math.Min(line.StartPoint.Y, line.EndPoint.Y);
var maxY = System.Math.Max(line.StartPoint.Y, line.EndPoint.Y);
touchesEdge = System.Math.Abs(minY - bounds.Bottom) < positionTolerance
|| System.Math.Abs(maxY - bounds.Top) < positionTolerance;
}
else if (isHorizontal)
{
var minX = System.Math.Min(line.StartPoint.X, line.EndPoint.X);
var maxX = System.Math.Max(line.StartPoint.X, line.EndPoint.X);
touchesEdge = System.Math.Abs(minX - bounds.Left) < positionTolerance
|| System.Math.Abs(maxX - bounds.Right) < positionTolerance;
}
if (touchesEdge)
flagged.Add(line.Id);
}
}
private static void DetectTitleBlockRegion(List<Entity> entities, CadDocument document, HashSet<Guid> flagged)
{
var textPositions = ExtractTextPositions(document);
if (textPositions.Count < 3) return;
var unflagged = entities.Where(e => !flagged.Contains(e.Id)).ToList();
if (unflagged.Count == 0) return;
var bounds = entities.GetBoundingBox();
if (bounds == null || bounds.Area() < OpenNest.Math.Tolerance.Epsilon) return;
var bestRegion = FindBestTitleBlockRegion(bounds, textPositions, unflagged);
if (bestRegion == null) return;
var initiallyInside = unflagged.Where(e => {
var c = EntityCenter(e);
return c.HasValue && RegionContains(bestRegion, c.Value);
}).ToList();
var expandedBounds = initiallyInside.Count > 0 ? initiallyInside.GetBoundingBox() : null;
foreach (var entity in unflagged)
{
var center = EntityCenter(entity);
if (!center.HasValue) continue;
if (RegionContains(bestRegion, center.Value)
|| (expandedBounds != null && RegionContains(expandedBounds, center.Value)))
flagged.Add(entity.Id);
}
}
private static List<Vector> ExtractTextPositions(CadDocument document)
{
var positions = new List<Vector>();
foreach (var entity in document.Entities)
{
switch (entity)
{
case ACadSharp.Entities.MText mtext:
positions.Add(new Vector(mtext.InsertPoint.X, mtext.InsertPoint.Y));
break;
case ACadSharp.Entities.TextEntity text:
var pt = text.HorizontalAlignment != 0 || text.VerticalAlignment != 0
? text.AlignmentPoint : text.InsertPoint;
positions.Add(new Vector(pt.X, pt.Y));
break;
}
}
return positions;
}
private static Box FindBestTitleBlockRegion(Box bounds, List<Vector> textPositions, List<Entity> entities)
{
var candidates = GenerateCandidateRegions(bounds);
Box bestRegion = null;
var bestScore = 0.0;
var openLines = FindOpenLines(entities);
foreach (var region in candidates)
{
var textCount = textPositions.Count(p => RegionContains(region, p));
if (textCount < 3) continue;
var openLineCount = openLines.Count(l => RegionContains(region, l.MidPoint));
var area = region.Area();
if (area < OpenNest.Math.Tolerance.Epsilon) continue;
var score = (double)textCount + openLineCount * 0.5;
var regionCenterX = (region.Left + region.Right) / 2;
var regionCenterY = (region.Bottom + region.Top) / 2;
if (regionCenterX > bounds.Center.X) score *= 1.3;
if (regionCenterY < bounds.Center.Y) score *= 1.3;
if (score > bestScore)
{
bestScore = score;
bestRegion = region;
}
}
return bestRegion;
}
private static List<Box> GenerateCandidateRegions(Box bounds)
{
var regions = new List<Box>();
var fractions = new[] { 0.25, 0.333, 0.5 };
foreach (var fx in fractions)
{
foreach (var fy in fractions)
{
var w = bounds.Length * fx;
var h = bounds.Width * fy;
regions.Add(new Box(bounds.Right - w, bounds.Bottom, w, h));
regions.Add(new Box(bounds.Left, bounds.Bottom, w, h));
regions.Add(new Box(bounds.Right - w, bounds.Top - h, w, h));
regions.Add(new Box(bounds.Left, bounds.Top - h, w, h));
}
}
foreach (var fy in fractions)
{
var h = bounds.Width * fy;
regions.Add(new Box(bounds.Left, bounds.Bottom, bounds.Length, h));
}
foreach (var fx in fractions)
{
var w = bounds.Length * fx;
regions.Add(new Box(bounds.Right - w, bounds.Bottom, w, bounds.Width));
}
return regions;
}
private static List<Line> FindOpenLines(List<Entity> entities)
{
var endpointUsers = new Dictionary<long, int>();
foreach (var entity in entities)
{
foreach (var ep in GetEntityEndpoints(entity))
{
var key = QuantizePoint(ep);
endpointUsers[key] = endpointUsers.GetValueOrDefault(key) + 1;
}
}
var openLines = new List<Line>();
foreach (var line in entities.OfType<Line>())
{
var startKey = QuantizePoint(line.StartPoint);
var endKey = QuantizePoint(line.EndPoint);
if (endpointUsers.GetValueOrDefault(startKey) <= 1 || endpointUsers.GetValueOrDefault(endKey) <= 1)
openLines.Add(line);
}
return openLines;
}
private static List<Vector> GetEntityEndpoints(Entity entity)
{
return entity switch
{
Line line => new List<Vector> { line.StartPoint, line.EndPoint },
Arc arc => new List<Vector> { arc.StartPoint(), arc.EndPoint() },
_ => new List<Vector>()
};
}
private static long QuantizePoint(Vector pt)
{
var qx = (long)(pt.X * 1000);
var qy = (long)(pt.Y * 1000);
return qx * 100000000L + qy;
}
private static Vector? EntityCenter(Entity entity)
{
return entity switch
{
Line line => line.MidPoint,
Arc arc => arc.Center,
Circle circle => circle.Center,
_ => null
};
}
private static bool RegionContains(Box box, Vector pt)
{
return pt.X >= box.Left && pt.X <= box.Right
&& pt.Y >= box.Bottom && pt.Y <= box.Top;
}
}
}
-16
View File
@@ -134,21 +134,5 @@ namespace OpenNest.Tests.IO
Assert.NotNull(drawing.Program);
Assert.NotNull(drawing.SourceEntities);
}
[Fact]
public void Import_WhenDetectTitleBlockTrue_PopulatesTitleBlockEntityIds()
{
var result = CadImporter.Import(TestDxf);
Assert.NotNull(result.TitleBlockEntityIds);
}
[Fact]
public void Import_WhenDetectTitleBlockFalse_TitleBlockEntityIdsIsNull()
{
var result = CadImporter.Import(TestDxf, new CadImportOptions { DetectTitleBlock = false });
Assert.Null(result.TitleBlockEntityIds);
}
}
}
@@ -1,245 +0,0 @@
using System.Collections.Generic;
using CSMath;
using OpenNest.Geometry;
using OpenNest.IO;
using Xunit;
namespace OpenNest.Tests.IO
{
public class TitleBlockDetectorTests
{
private static Line MakeLine(double x1, double y1, double x2, double y2) =>
new Line(x1, y1, x2, y2);
[Fact]
public void DetectByLayerName_FlagsTitleLayer()
{
var line = MakeLine(0, 0, 10, 0);
line.Layer = new Layer("TITLE");
var entities = new List<Entity> { line };
var result = TitleBlockDetector.Detect(entities, null);
Assert.Contains(line.Id, result);
}
[Fact]
public void DetectByLayerName_CaseInsensitive()
{
var line = MakeLine(0, 0, 10, 0);
line.Layer = new Layer("border");
var entities = new List<Entity> { line };
var result = TitleBlockDetector.Detect(entities, null);
Assert.Contains(line.Id, result);
}
[Fact]
public void DetectByLayerName_IgnoresNonMatchingLayers()
{
var line = MakeLine(0, 0, 10, 0);
line.Layer = new Layer("0");
var entities = new List<Entity> { line };
var result = TitleBlockDetector.Detect(entities, null);
Assert.DoesNotContain(line.Id, result);
}
[Theory]
[InlineData("TITLE")]
[InlineData("TITLEBLOCK")]
[InlineData("TITLE_BLOCK")]
[InlineData("BORDER")]
[InlineData("FRAME")]
[InlineData("TB")]
[InlineData("INFO")]
[InlineData("SHEET")]
[InlineData("ANNOTATION")]
public void DetectByLayerName_AllKnownNames(string layerName)
{
var line = MakeLine(0, 0, 10, 0);
line.Layer = new Layer(layerName);
var entities = new List<Entity> { line };
var result = TitleBlockDetector.Detect(entities, null);
Assert.Contains(line.Id, result);
}
[Fact]
public void DetectBorder_FlagsLinesOnBoundingBoxEdges()
{
var entities = new List<Entity>
{
new Line(0, 0, 86, 0) { Layer = new Layer("0") },
new Line(86, 0, 86, 134) { Layer = new Layer("0") },
new Line(86, 134, 0, 134) { Layer = new Layer("0") },
new Line(0, 134, 0, 0) { Layer = new Layer("0") },
new Line(30, 40, 50, 90) { Layer = new Layer("0") },
new Line(50, 90, 70, 40) { Layer = new Layer("0") },
new Line(70, 40, 30, 40) { Layer = new Layer("0") },
};
var result = TitleBlockDetector.Detect(entities, null);
Assert.Contains(entities[0].Id, result);
Assert.Contains(entities[1].Id, result);
Assert.Contains(entities[2].Id, result);
Assert.Contains(entities[3].Id, result);
Assert.DoesNotContain(entities[4].Id, result);
Assert.DoesNotContain(entities[5].Id, result);
Assert.DoesNotContain(entities[6].Id, result);
}
[Fact]
public void DetectBorder_FlagsZoneMarkerTicks()
{
var entities = new List<Entity>
{
new Line(0, 0, 100, 0) { Layer = new Layer("0") },
new Line(100, 0, 100, 80) { Layer = new Layer("0") },
new Line(100, 80, 0, 80) { Layer = new Layer("0") },
new Line(0, 80, 0, 0) { Layer = new Layer("0") },
new Line(25, 80, 25, 77) { Layer = new Layer("0") },
new Line(50, 80, 50, 77) { Layer = new Layer("0") },
new Line(75, 80, 75, 77) { Layer = new Layer("0") },
new Line(40, 30, 60, 30) { Layer = new Layer("0") },
};
var result = TitleBlockDetector.Detect(entities, null);
Assert.Contains(entities[4].Id, result);
Assert.Contains(entities[5].Id, result);
Assert.Contains(entities[6].Id, result);
Assert.DoesNotContain(entities[7].Id, result);
}
[Fact]
public void DetectBorder_IgnoresWhenNoBorderPresent()
{
var entities = new List<Entity>
{
new Line(30, 40, 50, 90) { Layer = new Layer("0") },
new Line(50, 90, 70, 40) { Layer = new Layer("0") },
new Line(70, 40, 30, 40) { Layer = new Layer("0") },
};
var result = TitleBlockDetector.Detect(entities, null);
Assert.Empty(result);
}
[Fact]
public void DetectBorder_ToleratesSlightRotation()
{
var angleRad = OpenNest.Math.Angle.ToRadians(0.5);
var endY = 86 * System.Math.Sin(angleRad);
var entities = new List<Entity>
{
new Line(0, 0, 86, endY) { Layer = new Layer("0") },
new Line(86, endY, 86, 134) { Layer = new Layer("0") },
new Line(86, 134, 0, 134) { Layer = new Layer("0") },
new Line(0, 134, 0, 0) { Layer = new Layer("0") },
new Line(30, 40, 50, 90) { Layer = new Layer("0") },
};
var result = TitleBlockDetector.Detect(entities, null);
Assert.Contains(entities[0].Id, result);
}
[Fact]
public void DetectTitleBlock_FlagsEntitiesInTextDenseCorner()
{
var partLine1 = new Line(5, 70, 25, 120) { Layer = new Layer("0") };
var partLine2 = new Line(25, 120, 45, 70) { Layer = new Layer("0") };
var partLine3 = new Line(45, 70, 5, 70) { Layer = new Layer("0") };
var tbLines = new List<Entity>();
for (var x = 50; x <= 85; x += 5)
tbLines.Add(new Line(x, 0, x, 30) { Layer = new Layer("0") });
for (var y = 0; y <= 30; y += 5)
tbLines.Add(new Line(50, y, 85, y) { Layer = new Layer("0") });
var entities = new List<Entity> { partLine1, partLine2, partLine3 };
entities.AddRange(tbLines);
var doc = BuildDocWithTexts(
(60, 5, "TITLE: Test Part"),
(60, 10, "DWG NO: 12345"),
(60, 15, "SCALE: 1:1"),
(60, 20, "REV: A"),
(60, 25, "MATERIAL: STEEL"));
var result = TitleBlockDetector.Detect(entities, doc);
foreach (var tb in tbLines)
Assert.Contains(tb.Id, result);
Assert.DoesNotContain(partLine1.Id, result);
Assert.DoesNotContain(partLine2.Id, result);
Assert.DoesNotContain(partLine3.Id, result);
}
[Fact]
public void DetectTitleBlock_NoFalsePositivesWithoutText()
{
var entities = new List<Entity>
{
new Line(30, 40, 50, 90) { Layer = new Layer("0") },
new Line(50, 90, 70, 40) { Layer = new Layer("0") },
new Line(70, 40, 30, 40) { Layer = new Layer("0") },
};
var result = TitleBlockDetector.Detect(entities, null);
Assert.Empty(result);
}
[Fact]
public void DetectTitleBlock_BottomEdgeStrip()
{
var partLine = new Line(20, 40, 80, 40) { Layer = new Layer("0") };
var tbLines = new List<Entity>();
for (var x = 0; x <= 100; x += 10)
tbLines.Add(new Line(x, 0, x, 20) { Layer = new Layer("0") });
for (var y = 0; y <= 20; y += 5)
tbLines.Add(new Line(0, y, 100, y) { Layer = new Layer("0") });
var entities = new List<Entity> { partLine };
entities.AddRange(tbLines);
var doc = BuildDocWithTexts(
(10, 5, "TITLE"),
(30, 5, "DWG NO"),
(50, 5, "SCALE"),
(70, 5, "REV"),
(90, 5, "DATE"));
var result = TitleBlockDetector.Detect(entities, doc);
foreach (var tb in tbLines)
Assert.Contains(tb.Id, result);
Assert.DoesNotContain(partLine.Id, result);
}
private static ACadSharp.CadDocument BuildDocWithTexts(
params (double x, double y, string value)[] texts)
{
var doc = new ACadSharp.CadDocument();
foreach (var (x, y, value) in texts)
{
var mtext = new ACadSharp.Entities.MText
{
InsertPoint = new XYZ(x, y, 0),
Value = value,
Height = 2.0
};
doc.Entities.Add(mtext);
}
return doc;
}
}
}
+18
View File
@@ -16,11 +16,15 @@ namespace OpenNest.Actions
private CutOffSettings settings;
private CutOffAxis lockedAxis = CutOffAxis.Vertical;
private Dictionary<Part, Entity> perimeterCache;
private readonly Timer debounceTimer;
private bool regeneratePending;
public ActionCutOff(PlateView plateView)
: base(plateView)
{
settings = plateView.CutOffSettings;
debounceTimer = new Timer { Interval = 16 };
debounceTimer.Tick += OnDebounce;
ConnectEvents();
}
@@ -36,6 +40,8 @@ namespace OpenNest.Actions
public override void DisconnectEvents()
{
debounceTimer.Stop();
debounceTimer.Dispose();
plateView.MouseMove -= OnMouseMove;
plateView.MouseDown -= OnMouseDown;
plateView.KeyDown -= OnKeyDown;
@@ -52,6 +58,18 @@ namespace OpenNest.Actions
private void OnMouseMove(object sender, MouseEventArgs e)
{
regeneratePending = true;
debounceTimer.Start();
}
private void OnDebounce(object sender, System.EventArgs e)
{
debounceTimer.Stop();
if (!regeneratePending)
return;
regeneratePending = false;
var pt = plateView.CurrentPoint;
previewCutOff = new CutOff(pt, lockedAxis);
previewCutOff.Regenerate(plateView.Plate, settings, perimeterCache);
+1 -46
View File
@@ -30,16 +30,13 @@ namespace OpenNest.Controls
public List<Entity> OriginalEntities { get; set; }
public bool ShowEntityLabels { get; set; }
public List<CadText> Texts { get; set; } = new List<CadText>();
public HashSet<Guid> TitleBlockEntityIds { get; set; }
private readonly Pen gridPen = new Pen(Color.FromArgb(70, 70, 70));
private readonly Dictionary<int, Pen> penCache = new Dictionary<int, Pen>();
private readonly Dictionary<int, Pen> ghostPenCache = new Dictionary<int, Pen>();
private readonly Font labelFont = new Font("Segoe UI", 7f);
private readonly SolidBrush labelBrush = new SolidBrush(Color.FromArgb(220, 255, 255, 200));
private readonly SolidBrush labelBackBrush = new SolidBrush(Color.FromArgb(33, 40, 48));
private readonly SolidBrush textBrush = new SolidBrush(Color.FromArgb(180, 200, 200, 200));
private readonly SolidBrush ghostTextBrush = new SolidBrush(Color.FromArgb(50, 200, 200, 200));
public event EventHandler<Line> LinePicked;
public event EventHandler PickCancelled;
@@ -105,13 +102,6 @@ namespace OpenNest.Controls
foreach (var entity in Entities)
{
if (IsEtchLayer(entity.Layer)) continue;
if (TitleBlockEntityIds != null && TitleBlockEntityIds.Contains(entity.Id))
{
DrawGhostEntity(e.Graphics, entity);
continue;
}
var isHighlighted = simplifierHighlightSet != null && simplifierHighlightSet.Contains(entity);
var pen = isHighlighted
? GetEntityPen(Color.FromArgb(60, entity.Color))
@@ -253,26 +243,11 @@ namespace OpenNest.Controls
return pen;
}
private Pen GetGhostPen(Color color)
{
var ghostColor = Color.FromArgb(60, color.R, color.G, color.B);
var argb = ghostColor.ToArgb();
if (!ghostPenCache.TryGetValue(argb, out var pen))
{
pen = new Pen(ghostColor);
ghostPenCache[argb] = pen;
}
return pen;
}
public void ClearPenCache()
{
foreach (var pen in penCache.Values)
pen.Dispose();
penCache.Clear();
foreach (var pen in ghostPenCache.Values)
pen.Dispose();
ghostPenCache.Clear();
}
private static bool IsEtchLayer(Layer layer) =>
@@ -438,28 +413,10 @@ namespace OpenNest.Controls
labelBrush.Dispose();
labelBackBrush.Dispose();
textBrush.Dispose();
ghostTextBrush.Dispose();
}
base.Dispose(disposing);
}
private void DrawGhostEntity(Graphics g, Entity e)
{
var pen = GetGhostPen(e.Color);
switch (e.Type)
{
case EntityType.Arc:
DrawArc(g, (Arc)e, pen);
break;
case EntityType.Circle:
DrawCircle(g, (Circle)e, pen);
break;
case EntityType.Line:
DrawLine(g, (Line)e, pen);
break;
}
}
private void DrawEntity(Graphics g, Entity e, Pen pen)
{
if (!e.Layer.IsVisible || !e.IsVisible)
@@ -544,10 +501,8 @@ namespace OpenNest.Controls
sf.Alignment = text.HAlign;
sf.LineAlignment = text.VAlign;
var brush = TitleBlockEntityIds != null && TitleBlockEntityIds.Count > 0
? ghostTextBrush : textBrush;
using var font = new Font("Segoe UI", fontSize, GraphicsUnit.Pixel);
g.DrawString(text.Value, font, brush, 0, 0, sf);
g.DrawString(text.Value, font, textBrush, 0, 0, sf);
g.Restore(state);
}
}
-1
View File
@@ -20,7 +20,6 @@ namespace OpenNest.Controls
public List<Entity> OriginalEntities { get; set; }
public List<Bend> Bends { get; set; } = new();
public HashSet<Guid> SuppressedEntityIds { get; set; }
public HashSet<Guid> TitleBlockEntityIds { get; set; }
public Box Bounds { get; set; }
public int EntityCount { get; set; }
public List<CadText> Texts { get; set; } = new();
-9
View File
@@ -94,16 +94,8 @@ namespace OpenNest.Forms
Bounds = result.Bounds,
EntityCount = result.Entities.Count,
Texts = ExtractTexts(result.Document),
TitleBlockEntityIds = result.TitleBlockEntityIds,
};
if (result.TitleBlockEntityIds != null && result.TitleBlockEntityIds.Count > 0)
{
item.SuppressedEntityIds ??= new HashSet<Guid>();
foreach (var id in result.TitleBlockEntityIds)
item.SuppressedEntityIds.Add(id);
}
if (InvokeRequired)
BeginInvoke((Action)(() => fileList.AddItem(item)));
else
@@ -162,7 +154,6 @@ namespace OpenNest.Forms
entityView1.Entities.AddRange(item.Entities);
entityView1.Bends = item.Bends ?? new List<Bend>();
entityView1.Texts = item.Texts ?? new List<CadText>();
entityView1.TitleBlockEntityIds = item.TitleBlockEntityIds;
item.Entities.ForEach(e => e.IsVisible = true);
if (item.Entities.Any(e => e.Layer != null))