diff --git a/src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs b/src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs new file mode 100644 index 0000000..8fd736b --- /dev/null +++ b/src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs @@ -0,0 +1,36 @@ +namespace Kompas.Mcp.Core.Modeling; + +/// Чистые помощники геометрии эскиза: маппинги контракта в параметры COM и валидация. +public static class SketchGeometry +{ + /// Направление дуги для ksArcByAngle: против часовой → 1, по часовой → -1. + public static short ArcDirection(bool counterClockwise) => (short)(counterClockwise ? 1 : -1); + + /// describe для ksRegularPolygon: вписанный (вершины на окружности) → false; описанный → true. + public static bool PolygonDescribe(bool inscribed) => !inscribed; + + /// Требовать строго положительное значение (радиус, полуось). + public static void RequirePositive(double value, string paramName) + { + if (!(value > 0)) + throw new ArgumentOutOfRangeException(paramName, value, "Значение должно быть > 0."); + } + + /// Требовать >= 3 вершин для правильного многоугольника. + public static void RequireVertexCount(int count) + { + if (count < 3) + throw new ArgumentOutOfRangeException(nameof(count), count, "Число вершин должно быть >= 3."); + } + + /// Валидировать список точек: не null, минимум , все координаты конечны. + public static void RequirePoints(IReadOnlyList<(double x, double y)> points, int min, string paramName) + { + ArgumentNullException.ThrowIfNull(points, paramName); + if (points.Count < min) + throw new ArgumentException($"Нужно минимум {min} точек, передано {points.Count}.", paramName); + for (int i = 0; i < points.Count; i++) + if (!double.IsFinite(points[i].x) || !double.IsFinite(points[i].y)) + throw new ArgumentException($"Точка [{i}] имеет неконечную координату.", paramName); + } +} diff --git a/src/Kompas.Mcp.Host/Tools/SketchPoint.cs b/src/Kompas.Mcp.Host/Tools/SketchPoint.cs new file mode 100644 index 0000000..40c78b6 --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/SketchPoint.cs @@ -0,0 +1,10 @@ +using System.ComponentModel; +using System.Text.Json.Serialization; + +namespace Kompas.Mcp.Host.Tools; + +/// Точка эскиза в его плоскости (мм). Элемент списка для ломаной/сплайна. +/// JSON-имена зафиксированы lowercase (x/y), чтобы схема инструмента совпадала с контрактом. +public sealed record SketchPoint( + [property: JsonPropertyName("x")][property: Description("Координата X в плоскости эскиза, мм")] double X, + [property: JsonPropertyName("y")][property: Description("Координата Y в плоскости эскиза, мм")] double Y); diff --git a/tests/Kompas.Mcp.Tests/SketchGeometryTests.cs b/tests/Kompas.Mcp.Tests/SketchGeometryTests.cs new file mode 100644 index 0000000..7921ee7 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/SketchGeometryTests.cs @@ -0,0 +1,54 @@ +using Kompas.Mcp.Core.Modeling; + +namespace Kompas.Mcp.Tests; + +[Trait("Category", "Unit")] +public sealed class SketchGeometryTests +{ + [Theory] + [InlineData(true, 1)] + [InlineData(false, -1)] + public void ArcDirection_maps_orientation(bool ccw, int expected) + => Assert.Equal(expected, (int)SketchGeometry.ArcDirection(ccw)); + + [Theory] + [InlineData(true, false)] // вписанный → describe=false + [InlineData(false, true)] // описанный → describe=true + public void PolygonDescribe_inverts_inscribed(bool inscribed, bool expected) + => Assert.Equal(expected, SketchGeometry.PolygonDescribe(inscribed)); + + [Fact] + public void RequirePositive_throws_on_zero_or_negative() + { + Assert.Throws(() => SketchGeometry.RequirePositive(0, "radius")); + Assert.Throws(() => SketchGeometry.RequirePositive(-1, "radius")); + Assert.Null(Record.Exception(() => SketchGeometry.RequirePositive(0.1, "radius"))); + } + + [Fact] + public void RequireVertexCount_throws_below_three() + { + Assert.Throws(() => SketchGeometry.RequireVertexCount(2)); + Assert.Null(Record.Exception(() => SketchGeometry.RequireVertexCount(3))); + } + + [Fact] + public void RequirePoints_throws_on_null() + => Assert.Throws( + () => SketchGeometry.RequirePoints(null!, 2, "points")); + + [Fact] + public void RequirePoints_throws_when_too_few() + => Assert.Throws( + () => SketchGeometry.RequirePoints(new[] { (0.0, 0.0) }, 2, "points")); + + [Fact] + public void RequirePoints_throws_on_non_finite() + => Assert.Throws( + () => SketchGeometry.RequirePoints(new[] { (0.0, 0.0), (double.NaN, 1.0) }, 2, "points")); + + [Fact] + public void RequirePoints_passes_on_valid() + => Assert.Null(Record.Exception( + () => SketchGeometry.RequirePoints(new[] { (0.0, 0.0), (1.0, 1.0) }, 2, "points"))); +}