From 6e5471271d10de453f4743db735337b65b229277 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 17 Mar 2026 08:07:22 -0400 Subject: [PATCH] feat(core): add RoundedRectangleShape Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Core/Shapes/RoundedRectangleShape.cs | 59 +++++++++++++++++++ .../Shapes/RoundedRectangleShapeTests.cs | 38 ++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 OpenNest.Core/Shapes/RoundedRectangleShape.cs create mode 100644 OpenNest.Tests/Shapes/RoundedRectangleShapeTests.cs diff --git a/OpenNest.Core/Shapes/RoundedRectangleShape.cs b/OpenNest.Core/Shapes/RoundedRectangleShape.cs new file mode 100644 index 0000000..1294ffa --- /dev/null +++ b/OpenNest.Core/Shapes/RoundedRectangleShape.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using OpenNest.Geometry; +using OpenNest.Math; + +namespace OpenNest.Shapes +{ + public class RoundedRectangleShape : ShapeDefinition + { + public double Width { get; set; } + public double Height { get; set; } + public double Radius { get; set; } + + public override Drawing GetDrawing() + { + var r = Radius; + var entities = new List(); + + if (r <= 0) + { + entities.Add(new Line(0, 0, Width, 0)); + entities.Add(new Line(Width, 0, Width, Height)); + entities.Add(new Line(Width, Height, 0, Height)); + entities.Add(new Line(0, Height, 0, 0)); + } + else + { + // Bottom edge (left to right, above bottom-left arc to bottom-right arc) + entities.Add(new Line(r, 0, Width - r, 0)); + + // Bottom-right corner arc: center at (Width-r, r), from 270deg to 360deg + entities.Add(new Arc(Width - r, r, r, + Angle.ToRadians(270), Angle.ToRadians(360))); + + // Right edge + entities.Add(new Line(Width, r, Width, Height - r)); + + // Top-right corner arc: center at (Width-r, Height-r), from 0deg to 90deg + entities.Add(new Arc(Width - r, Height - r, r, + Angle.ToRadians(0), Angle.ToRadians(90))); + + // Top edge (right to left) + entities.Add(new Line(Width - r, Height, r, Height)); + + // Top-left corner arc: center at (r, Height-r), from 90deg to 180deg + entities.Add(new Arc(r, Height - r, r, + Angle.ToRadians(90), Angle.ToRadians(180))); + + // Left edge + entities.Add(new Line(0, Height - r, 0, r)); + + // Bottom-left corner arc: center at (r, r), from 180deg to 270deg + entities.Add(new Arc(r, r, r, + Angle.ToRadians(180), Angle.ToRadians(270))); + } + + return CreateDrawing(entities); + } + } +} diff --git a/OpenNest.Tests/Shapes/RoundedRectangleShapeTests.cs b/OpenNest.Tests/Shapes/RoundedRectangleShapeTests.cs new file mode 100644 index 0000000..3880b18 --- /dev/null +++ b/OpenNest.Tests/Shapes/RoundedRectangleShapeTests.cs @@ -0,0 +1,38 @@ +using OpenNest.Shapes; + +namespace OpenNest.Tests.Shapes; + +public class RoundedRectangleShapeTests +{ + [Fact] + public void GetDrawing_BoundingBoxMatchesDimensions() + { + var shape = new RoundedRectangleShape { Width = 20, Height = 10, Radius = 2 }; + var drawing = shape.GetDrawing(); + + var bbox = drawing.Program.BoundingBox(); + Assert.Equal(20, bbox.Width, 0.1); + Assert.Equal(10, bbox.Length, 0.1); + } + + [Fact] + public void GetDrawing_AreaIsLessThanFullRectangle() + { + var shape = new RoundedRectangleShape { Width = 20, Height = 10, Radius = 2 }; + var drawing = shape.GetDrawing(); + + // Area should be less than 20*10=200 because corners are rounded + // Area = W*H - (4 - pi) * r^2 = 200 - (4 - pi) * 4 ~ 196.57 + Assert.True(drawing.Area < 200); + Assert.True(drawing.Area > 190); + } + + [Fact] + public void GetDrawing_ZeroRadius_MatchesRectangleArea() + { + var shape = new RoundedRectangleShape { Width = 20, Height = 10, Radius = 0 }; + var drawing = shape.GetDrawing(); + + Assert.Equal(200, drawing.Area, 0.5); + } +}