From 0bea3a0cf339c53b7d0e6cf0c07cafa245ff3060 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Tue, 26 May 2026 21:47:05 +0300 Subject: [PATCH] =?UTF-8?q?feat(core):=20SketchGeometry=20(=D0=BC=D0=B0?= =?UTF-8?q?=D0=BF=D0=BF=D0=B8=D0=BD=D0=B3=D0=B8/=D0=B2=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0=D1=86=D0=B8=D1=8F)=20+=20SketchPoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ArcDirection, PolygonDescribe, RequirePositive, RequireVertexCount, RequirePoints (null + finite). SketchPoint record с JSON x/y. 10 unit-тестов. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Modeling/SketchGeometry.cs | 36 +++++++++++++ src/Kompas.Mcp.Host/Tools/SketchPoint.cs | 10 ++++ tests/Kompas.Mcp.Tests/SketchGeometryTests.cs | 54 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs create mode 100644 src/Kompas.Mcp.Host/Tools/SketchPoint.cs create mode 100644 tests/Kompas.Mcp.Tests/SketchGeometryTests.cs 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"))); +}