Files
OpenNest/OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs
AJ Isaacs 356b989424 feat: mirror axis simplifier, bend note propagation, ellipse fixes
Geometry Simplifier:
- Replace least-squares circle fitting with mirror axis algorithm
  that constrains center to perpendicular bisector of chord, guaranteeing
  zero-gap endpoint connectivity by construction
- Golden section search optimizes center position along the axis
- Increase default tolerance from 0.005 to 0.5 for practical CNC use
- Support existing arcs in simplification runs (sample arc points to
  find larger replacement arcs spanning lines + arcs together)
- Add tolerance zone visualization (offset original geometry ±tolerance)
- Show original geometry overlay with orange dashed lines in preview
- Add "Original" checkbox to CadConverter for comparing old vs new
- Store OriginalEntities on FileListItem to prevent tolerance creep
  when re-running simplifier with different settings

Bend Detection:
- Propagate bend notes to collinear bend lines split by cutouts
  using infinite-line perpendicular distance check
- Add bend note text rendering in EntityView at bend line midpoints

DXF Import:
- Fix trimmed ellipse closing chord: only close when sweep ≈ 2π,
  preventing phantom lines through slot cutouts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:27:46 -04:00

133 lines
4.7 KiB
C#

using ACadSharp.IO;
using OpenNest.Bending;
using OpenNest.IO.Bending;
namespace OpenNest.Tests.Bending;
public class SolidWorksBendDetectorTests
{
[Fact]
public void SolidWorksDetector_IsRegistered()
{
var detector = BendDetectorRegistry.GetByName("SolidWorks");
Assert.NotNull(detector);
Assert.Equal("SolidWorks", detector.Name);
}
[Fact]
public void Registry_ContainsSolidWorksDetector()
{
Assert.Contains(BendDetectorRegistry.Detectors,
d => d.Name == "SolidWorks");
}
[Fact]
public void AutoDetect_EmptyDocument_ReturnsEmptyList()
{
var doc = new ACadSharp.CadDocument();
var bends = BendDetectorRegistry.AutoDetect(doc);
Assert.Empty(bends);
}
[Fact]
public void Simplifier_EllipseSegments_FewLargeArcs()
{
var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT11 Test.dxf");
Assert.True(File.Exists(path), $"Test DXF not found: {path}");
var importer = new OpenNest.IO.DxfImporter { SplinePrecision = 200 };
var result = importer.Import(path);
var shape = new OpenNest.Geometry.Shape();
shape.Entities.AddRange(result.Entities);
// Default tolerance is 0.5 — should produce very few large arcs
var simplifier = new OpenNest.Geometry.GeometrySimplifier();
var candidates = simplifier.Analyze(shape);
// With 0.5 tolerance, 2 ellipses (~400 segments) should reduce to a handful of arcs
// Dump for visibility then assert
var info = string.Join(", ", candidates.Select(c => $"[{c.StartIndex}..{c.EndIndex}]={c.LineCount}lines R={c.FittedArc.Radius:F3}"));
Assert.True(candidates.Count <= 10,
$"Expected <=10 arcs but got {candidates.Count}: {info}");
// Each arc should cover many lines
foreach (var c in candidates)
Assert.True(c.LineCount >= 3, $"Arc [{c.StartIndex}..{c.EndIndex}] only covers {c.LineCount} lines");
// Arcs should connect to the original geometry within tolerance
foreach (var c in candidates)
{
var firstLine = (OpenNest.Geometry.Line)shape.Entities[c.StartIndex];
var lastLine = (OpenNest.Geometry.Line)shape.Entities[c.EndIndex];
var arc = c.FittedArc;
var startGap = firstLine.StartPoint.DistanceTo(arc.StartPoint());
var endGap = lastLine.EndPoint.DistanceTo(arc.EndPoint());
Assert.True(startGap < 1e-9, $"Start gap {startGap} at candidate [{c.StartIndex}..{c.EndIndex}]");
Assert.True(endGap < 1e-9, $"End gap {endGap} at candidate [{c.StartIndex}..{c.EndIndex}]");
}
}
[Fact]
public void Import_TrimmedEllipse_NoClosingChord()
{
var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT11.dxf");
Assert.True(File.Exists(path), $"Test DXF not found: {path}");
var importer = new OpenNest.IO.DxfImporter();
var result = importer.Import(path);
// The DXF has 2 trimmed ellipses forming an oblong slot.
// Trimmed ellipses must not generate a closing chord line.
// 83 = 72 lines + 4 arcs + 7 circles + ellipse segments (heavily merged by optimizer)
Assert.Equal(83, result.Entities.Count);
}
[Fact]
public void DetectBends_SplitBendLine_PropagatesNote()
{
var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT23.dxf");
Assert.True(File.Exists(path), $"Test DXF not found: {path}");
using var reader = new DxfReader(path);
var doc = reader.Read();
var detector = new SolidWorksBendDetector();
var bends = detector.DetectBends(doc);
Assert.Equal(5, bends.Count);
Assert.All(bends, b =>
{
Assert.NotNull(b.NoteText);
Assert.Equal(BendDirection.Up, b.Direction);
Assert.Equal(90.0, b.Angle);
Assert.Equal(0.125, b.Radius);
});
}
[Fact]
public void DetectBends_RealDxf_ParsesNotesCorrectly()
{
var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT45.dxf");
Assert.True(File.Exists(path), $"Test DXF not found: {path}");
using var reader = new DxfReader(path);
var doc = reader.Read();
var detector = new SolidWorksBendDetector();
var bends = detector.DetectBends(doc);
Assert.Equal(2, bends.Count);
foreach (var bend in bends)
{
Assert.NotNull(bend.NoteText);
Assert.Equal(BendDirection.Up, bend.Direction);
Assert.Equal(90.0, bend.Angle);
Assert.Equal(0.313, bend.Radius);
}
}
}