Compare commits

2 Commits

Author SHA1 Message Date
aj d215d02844 style(shapes): remove redundant usings and document PipeSizes bound 2026-04-10 17:31:22 -04:00
aj 57863e16e9 feat(shapes): add ANSI pipe OD lookup table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 17:27:25 -04:00
2 changed files with 142 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
using System.Collections.Generic;
namespace OpenNest.Shapes
{
public static class PipeSizes
{
public readonly record struct Entry(string Label, double OuterDiameter);
public static IReadOnlyList<Entry> All { get; } = new[]
{
new Entry("1/8", 0.405),
new Entry("1/4", 0.540),
new Entry("3/8", 0.675),
new Entry("1/2", 0.840),
new Entry("3/4", 1.050),
new Entry("1", 1.315),
new Entry("1 1/4", 1.660),
new Entry("1 1/2", 1.900),
new Entry("2", 2.375),
new Entry("2 1/2", 2.875),
new Entry("3", 3.500),
new Entry("3 1/2", 4.000),
new Entry("4", 4.500),
new Entry("4 1/2", 5.000),
new Entry("5", 5.563),
new Entry("6", 6.625),
new Entry("7", 7.625),
new Entry("8", 8.625),
new Entry("9", 9.625),
new Entry("10", 10.750),
new Entry("11", 11.750),
new Entry("12", 12.750),
new Entry("14", 14.000),
new Entry("16", 16.000),
new Entry("18", 18.000),
new Entry("20", 20.000),
new Entry("24", 24.000),
new Entry("26", 26.000),
new Entry("28", 28.000),
new Entry("30", 30.000),
new Entry("32", 32.000),
new Entry("34", 34.000),
new Entry("36", 36.000),
new Entry("42", 42.000),
new Entry("48", 48.000),
};
public static bool TryGetOD(string label, out double outerDiameter)
{
foreach (var entry in All)
{
if (entry.Label == label)
{
outerDiameter = entry.OuterDiameter;
return true;
}
}
outerDiameter = 0;
return false;
}
/// <summary>
/// Returns all pipe sizes whose outer diameter is less than or equal to <paramref name="maxOD"/>.
/// The bound is inclusive.
/// </summary>
public static IEnumerable<Entry> GetFittingSizes(double maxOD)
{
foreach (var entry in All)
{
if (entry.OuterDiameter <= maxOD)
{
yield return entry;
}
}
}
}
}
+64
View File
@@ -0,0 +1,64 @@
using OpenNest.Shapes;
namespace OpenNest.Tests.Shapes;
public class PipeSizesTests
{
[Fact]
public void All_ContainsExpectedCount()
{
Assert.Equal(35, PipeSizes.All.Count);
}
[Fact]
public void All_IsSortedByOuterDiameterAscending()
{
for (var i = 1; i < PipeSizes.All.Count; i++)
Assert.True(PipeSizes.All[i].OuterDiameter > PipeSizes.All[i - 1].OuterDiameter);
}
[Theory]
[InlineData("1/8", 0.405)]
[InlineData("1/2", 0.840)]
[InlineData("2", 2.375)]
[InlineData("2 1/2", 2.875)]
[InlineData("12", 12.750)]
[InlineData("48", 48.000)]
public void TryGetOD_KnownLabel_ReturnsExpectedOD(string label, double expected)
{
Assert.True(PipeSizes.TryGetOD(label, out var od));
Assert.Equal(expected, od, 0.001);
}
[Fact]
public void TryGetOD_UnknownLabel_ReturnsFalse()
{
Assert.False(PipeSizes.TryGetOD("bogus", out _));
}
[Fact]
public void GetFittingSizes_FiltersByMaxOD()
{
var results = PipeSizes.GetFittingSizes(3.0).ToList();
Assert.Contains(results, e => e.Label == "2 1/2");
Assert.DoesNotContain(results, e => e.Label == "3");
Assert.DoesNotContain(results, e => e.Label == "4");
}
[Fact]
public void GetFittingSizes_ExactBoundary_IsInclusive()
{
// NPS 3 has OD 3.500; passing maxOD = 3.500 should include it.
var results = PipeSizes.GetFittingSizes(3.500).ToList();
Assert.Contains(results, e => e.Label == "3");
Assert.DoesNotContain(results, e => e.Label == "3 1/2");
}
[Fact]
public void GetFittingSizes_MaxSmallerThanSmallest_ReturnsEmpty()
{
Assert.Empty(PipeSizes.GetFittingSizes(0.1));
}
}