feat(core): SketchGeometry (маппинги/валидация) + SketchPoint

ArcDirection, PolygonDescribe, RequirePositive, RequireVertexCount,
RequirePoints (null + finite). SketchPoint record с JSON x/y. 10 unit-тестов.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 21:47:05 +03:00
parent a16ee31498
commit 0bea3a0cf3
3 changed files with 100 additions and 0 deletions
@@ -0,0 +1,36 @@
namespace Kompas.Mcp.Core.Modeling;
/// <summary>Чистые помощники геометрии эскиза: маппинги контракта в параметры COM и валидация.</summary>
public static class SketchGeometry
{
/// <summary>Направление дуги для ksArcByAngle: против часовой → 1, по часовой → -1.</summary>
public static short ArcDirection(bool counterClockwise) => (short)(counterClockwise ? 1 : -1);
/// <summary>describe для ksRegularPolygon: вписанный (вершины на окружности) → false; описанный → true.</summary>
public static bool PolygonDescribe(bool inscribed) => !inscribed;
/// <summary>Требовать строго положительное значение (радиус, полуось).</summary>
public static void RequirePositive(double value, string paramName)
{
if (!(value > 0))
throw new ArgumentOutOfRangeException(paramName, value, "Значение должно быть > 0.");
}
/// <summary>Требовать >= 3 вершин для правильного многоугольника.</summary>
public static void RequireVertexCount(int count)
{
if (count < 3)
throw new ArgumentOutOfRangeException(nameof(count), count, "Число вершин должно быть >= 3.");
}
/// <summary>Валидировать список точек: не null, минимум <paramref name="min"/>, все координаты конечны.</summary>
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);
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.ComponentModel;
using System.Text.Json.Serialization;
namespace Kompas.Mcp.Host.Tools;
/// <summary>Точка эскиза в его плоскости (мм). Элемент списка для ломаной/сплайна.
/// JSON-имена зафиксированы lowercase (x/y), чтобы схема инструмента совпадала с контрактом.</summary>
public sealed record SketchPoint(
[property: JsonPropertyName("x")][property: Description("Координата X в плоскости эскиза, мм")] double X,
[property: JsonPropertyName("y")][property: Description("Координата Y в плоскости эскиза, мм")] double Y);
@@ -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<ArgumentOutOfRangeException>(() => SketchGeometry.RequirePositive(0, "radius"));
Assert.Throws<ArgumentOutOfRangeException>(() => SketchGeometry.RequirePositive(-1, "radius"));
Assert.Null(Record.Exception(() => SketchGeometry.RequirePositive(0.1, "radius")));
}
[Fact]
public void RequireVertexCount_throws_below_three()
{
Assert.Throws<ArgumentOutOfRangeException>(() => SketchGeometry.RequireVertexCount(2));
Assert.Null(Record.Exception(() => SketchGeometry.RequireVertexCount(3)));
}
[Fact]
public void RequirePoints_throws_on_null()
=> Assert.Throws<ArgumentNullException>(
() => SketchGeometry.RequirePoints(null!, 2, "points"));
[Fact]
public void RequirePoints_throws_when_too_few()
=> Assert.Throws<ArgumentException>(
() => SketchGeometry.RequirePoints(new[] { (0.0, 0.0) }, 2, "points"));
[Fact]
public void RequirePoints_throws_on_non_finite()
=> Assert.Throws<ArgumentException>(
() => 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")));
}