feat(core): add FlangeShape with JSON preset loading
FlangeShape generates an outer circle with evenly-spaced bolt holes on a bolt circle pattern. ShapeDefinition.LoadFromJson<T>() provides generic JSON loading for any shape — no separate preset classes needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.Shapes
|
||||||
|
{
|
||||||
|
public class FlangeShape : ShapeDefinition
|
||||||
|
{
|
||||||
|
public double NominalPipeSize { get; set; }
|
||||||
|
public double OD { get; set; }
|
||||||
|
public double HoleDiameter { get; set; }
|
||||||
|
public double HolePatternDiameter { get; set; }
|
||||||
|
public int HoleCount { get; set; }
|
||||||
|
|
||||||
|
public override Drawing GetDrawing()
|
||||||
|
{
|
||||||
|
var entities = new List<Entity>();
|
||||||
|
|
||||||
|
// Outer circle
|
||||||
|
entities.Add(new Circle(0, 0, OD / 2.0));
|
||||||
|
|
||||||
|
// Bolt holes evenly spaced on the bolt circle
|
||||||
|
var boltCircleRadius = HolePatternDiameter / 2.0;
|
||||||
|
var holeRadius = HoleDiameter / 2.0;
|
||||||
|
var angleStep = 2.0 * System.Math.PI / HoleCount;
|
||||||
|
|
||||||
|
for (var i = 0; i < HoleCount; i++)
|
||||||
|
{
|
||||||
|
var angle = i * angleStep;
|
||||||
|
var cx = boltCircleRadius * System.Math.Cos(angle);
|
||||||
|
var cy = boltCircleRadius * System.Math.Sin(angle);
|
||||||
|
entities.Add(new Circle(cx, cy, holeRadius));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateDrawing(entities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
using OpenNest.Converters;
|
using OpenNest.Converters;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
@@ -7,6 +9,11 @@ namespace OpenNest.Shapes
|
|||||||
{
|
{
|
||||||
public abstract class ShapeDefinition
|
public abstract class ShapeDefinition
|
||||||
{
|
{
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
protected ShapeDefinition()
|
protected ShapeDefinition()
|
||||||
@@ -19,6 +26,12 @@ namespace OpenNest.Shapes
|
|||||||
|
|
||||||
public abstract Drawing GetDrawing();
|
public abstract Drawing GetDrawing();
|
||||||
|
|
||||||
|
public static List<T> LoadFromJson<T>(string path) where T : ShapeDefinition
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
return JsonSerializer.Deserialize<List<T>>(json, JsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
protected Drawing CreateDrawing(List<Entity> entities)
|
protected Drawing CreateDrawing(List<Entity> entities)
|
||||||
{
|
{
|
||||||
var pgm = ConvertGeometry.ToProgram(entities);
|
var pgm = ConvertGeometry.ToProgram(entities);
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using OpenNest.CNC;
|
||||||
|
using OpenNest.Shapes;
|
||||||
|
|
||||||
|
namespace OpenNest.Tests.Shapes;
|
||||||
|
|
||||||
|
public class FlangeShapeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetDrawing_BoundingBoxMatchesOD()
|
||||||
|
{
|
||||||
|
var shape = new FlangeShape
|
||||||
|
{
|
||||||
|
OD = 10,
|
||||||
|
HoleDiameter = 1,
|
||||||
|
HolePatternDiameter = 7,
|
||||||
|
HoleCount = 4
|
||||||
|
};
|
||||||
|
var drawing = shape.GetDrawing();
|
||||||
|
|
||||||
|
var bbox = drawing.Program.BoundingBox();
|
||||||
|
Assert.Equal(10, bbox.Width, 0.01);
|
||||||
|
Assert.Equal(10, bbox.Length, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDrawing_AreaExcludesBoltHoles()
|
||||||
|
{
|
||||||
|
var shape = new FlangeShape
|
||||||
|
{
|
||||||
|
OD = 10,
|
||||||
|
HoleDiameter = 1,
|
||||||
|
HolePatternDiameter = 7,
|
||||||
|
HoleCount = 4
|
||||||
|
};
|
||||||
|
var drawing = shape.GetDrawing();
|
||||||
|
|
||||||
|
// Area = pi * 5^2 - 4 * pi * 0.5^2 = pi * (25 - 1) = pi * 24
|
||||||
|
var expectedArea = System.Math.PI * 24;
|
||||||
|
Assert.Equal(expectedArea, drawing.Area, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDrawing_DefaultName_IsFlange()
|
||||||
|
{
|
||||||
|
var shape = new FlangeShape
|
||||||
|
{
|
||||||
|
OD = 10,
|
||||||
|
HoleDiameter = 1,
|
||||||
|
HolePatternDiameter = 7,
|
||||||
|
HoleCount = 4
|
||||||
|
};
|
||||||
|
var drawing = shape.GetDrawing();
|
||||||
|
|
||||||
|
Assert.Equal("Flange", drawing.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadFromJson_ProducesCorrectDrawing()
|
||||||
|
{
|
||||||
|
var json = """
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "2in-150#",
|
||||||
|
"NominalPipeSize": 2.0,
|
||||||
|
"OD": 6.0,
|
||||||
|
"HoleDiameter": 0.75,
|
||||||
|
"HolePatternDiameter": 4.75,
|
||||||
|
"HoleCount": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "2in-300#",
|
||||||
|
"NominalPipeSize": 2.0,
|
||||||
|
"OD": 6.5,
|
||||||
|
"HoleDiameter": 0.75,
|
||||||
|
"HolePatternDiameter": 5.0,
|
||||||
|
"HoleCount": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
""";
|
||||||
|
|
||||||
|
var tempFile = Path.GetTempFileName();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(tempFile, json);
|
||||||
|
|
||||||
|
var flanges = ShapeDefinition.LoadFromJson<FlangeShape>(tempFile);
|
||||||
|
|
||||||
|
Assert.Equal(2, flanges.Count);
|
||||||
|
|
||||||
|
var first = flanges[0];
|
||||||
|
Assert.Equal("2in-150#", first.Name);
|
||||||
|
var drawing = first.GetDrawing();
|
||||||
|
var bbox = drawing.Program.BoundingBox();
|
||||||
|
Assert.Equal(6, bbox.Width, 0.01);
|
||||||
|
|
||||||
|
var second = flanges[1];
|
||||||
|
Assert.Equal("2in-300#", second.Name);
|
||||||
|
Assert.Equal(8, second.HoleCount);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user