feat: drawing_add_diametral_dimension — диаметральный размер на виде (API7)

Веха 2D-ЧЕРТЁЖ, инкремент 4. DrawingService.AddDiametralDimensionAsync:
(ISymbols2DContainer)view.DiametralDimensions.Add → Xc/Yc/Radius/Angle(рад) +
AutoNominalValue → Update → Valid → NominalValue (= диаметр 2R). angle в градусах
(DimensionAngles.ToRadians). Новый инструмент drawing_add_diametral_dimension.

Рефактор по ревью Codex: извлечён RequireSymbols2DContainer(viewNumber) (общий для
линейных/диаметральных размеров); AutoNominalValue выставляется явно (как у линейного);
DimensionType — COM-дефолт; ToRadians — чистый helper с unit-тестом.

Валидаторы: RequirePositiveRadius (новый). Тесты: +10 unit (ToRadians,
RequirePositiveRadius), +4 integration (DrawingDiametralTests: диаметр=2R, адресация
вида, radius<=0, нет видов). Итого 141 unit + 76 integration = 217.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 18:30:04 +03:00
parent b6cd92b95c
commit 910d120c8a
8 changed files with 235 additions and 8 deletions
@@ -13,6 +13,18 @@ public enum DimensionOrientation
Parallel,
}
/// <summary>Конверсии углов размеров (2D-API КОМПАС использует радианы).</summary>
public static class DimensionAngles
{
/// <summary>Градусы → радианы (угол выноски в API задаётся в радианах).</summary>
public static double ToRadians(double degrees)
{
if (!double.IsFinite(degrees))
throw new ArgumentException("Угол должен быть конечным числом.", nameof(degrees));
return degrees * Math.PI / 180.0;
}
}
/// <summary>Маппинг ориентации размера ↔ перечисление КОМПАС и разбор строки.</summary>
public static class DimensionOrientations
{
+57 -7
View File
@@ -100,9 +100,7 @@ public sealed class DrawingService
DrawingValidation.RequireFiniteCoords(x1, y1, x2, y2, x3, y3);
DrawingValidation.RequireDistinctPoints(x1, y1, x2, y2);
var view = FindView(viewNumber);
var symbols = view as ISymbols2DContainer
?? throw new InvalidOperationException("Вид не приводится к ISymbols2DContainer.");
var (view, symbols) = RequireSymbols2DContainer(viewNumber);
var dim = symbols.LineDimensions?.Add()
?? throw new InvalidOperationException("LineDimensions.Add вернул null.");
try
@@ -136,11 +134,63 @@ public sealed class DrawingService
/// <summary>Число линейных размеров на виде по номеру (для проверки/диагностики). Размеры живут
/// в ISymbols2DContainer вида, а НЕ в IView.ObjectCount (тот считает геометрию).</summary>
public Task<int> GetViewLineDimensionCountAsync(int viewNumber, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.LineDimensions?.Count ?? 0, ct);
/// <summary>
/// Поставить диаметральный размер (Ø) окружности с центром (xc,yc) и радиусом <paramref name="radius"/>
/// (ЛОКАЛЬНАЯ СК вида, мм) на виде. <paramref name="angleDeg"/> — направление выноски (градусы).
/// Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида.
/// </summary>
public Task<DrawingDimensionResult> AddDiametralDimensionAsync(int viewNumber, double xc, double yc,
double radius, double angleDeg, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => AddDiametralDimensionCore(viewNumber, xc, yc, radius, angleDeg), ct);
private DrawingDimensionResult AddDiametralDimensionCore(int viewNumber, double xc, double yc,
double radius, double angleDeg)
{
DrawingValidation.RequireFiniteCoords(xc, yc);
DrawingValidation.RequirePositiveRadius(radius);
var angleRad = DimensionAngles.ToRadians(angleDeg);
var (view, symbols) = RequireSymbols2DContainer(viewNumber);
var dim = symbols.DiametralDimensions?.Add()
?? throw new InvalidOperationException("DiametralDimensions.Add вернул null.");
try
{
var symbols = (ISymbols2DContainer)FindView(viewNumber);
return symbols.LineDimensions?.Count ?? 0;
}, ct);
dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; dim.Angle = angleRad;
// DimensionType (стиль линии) не задаём — принимаем COM-дефолт (стандартный стиль).
((IDimensionText)dim).AutoNominalValue = true;
if (!dim.Update())
throw new InvalidOperationException("IDiametralDimension.Update вернул FALSE — размер не построен.");
if (!dim.Valid)
throw new InvalidOperationException("Диаметральный размер построен невалидным.");
var value = ((IDimensionText)dim).NominalValue; // диаметр = 2·radius
if (Math.Abs(value) < 1e-6)
throw new InvalidOperationException("Диаметральный размер измерил нулевое значение.");
return new DrawingDimensionResult { Value = value, ViewNumber = view.Number };
}
catch
{
try { dim.Delete(); } catch { /* лучшее усилие */ }
throw;
}
}
/// <summary>Число диаметральных размеров на виде (для проверки попадания в вид — они в
/// ISymbols2DContainer, а не в IView.ObjectCount).</summary>
public Task<int> GetViewDiametralDimensionCountAsync(int viewNumber, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.DiametralDimensions?.Count ?? 0, ct);
/// <summary>Вид + его ISymbols2DContainer (QI от вида). Общий для линейных/диаметральных/… размеров.</summary>
private (IView View, ISymbols2DContainer Symbols) RequireSymbols2DContainer(int viewNumber)
{
var view = FindView(viewNumber);
var symbols = view as ISymbols2DContainer
?? throw new InvalidOperationException("Вид не приводится к ISymbols2DContainer.");
return (view, symbols);
}
/// <summary>Найти вид: при viewNumber ≤ 0 — первый несистемный (Number != 0); иначе по номеру.</summary>
private IView FindView(int viewNumber)
@@ -41,6 +41,13 @@ public static class DrawingValidation
throw new ArgumentException("Координаты должны быть конечными числами (не NaN/Infinity).");
}
/// <summary>Проверить радиус: конечный и строго положительный.</summary>
public static void RequirePositiveRadius(double radius)
{
if (!double.IsFinite(radius) || radius <= 0)
throw new ArgumentOutOfRangeException(nameof(radius), "Радиус должен быть конечным и > 0.");
}
/// <summary>Минимальное расстояние между выносными точками линейного размера (мм).</summary>
private const double MinPointDistance = 1e-7;
+18
View File
@@ -65,4 +65,22 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
var r = await drawing.AddLinearDimensionAsync(viewNumber, x1, y1, x2, y2, x3, y3, o);
return $"Линейный размер ({orientation}) поставлен на виде {r.ViewNumber}, значение {r.Value:0.###} мм.";
}
[McpServerTool(Name = "drawing_add_diametral_dimension")]
[Description("Поставить диаметральный размер (Ø) окружности на виде активного чертежа. Окружность " +
"задаётся центром (xc,yc) и радиусом radius в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты " +
"модели на плоскости вида) — задайте параметры реальной окружности вида. angle — направление выноски " +
"в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). " +
"Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида.")]
public async Task<string> AddDiametralDimension(
[Description("X центра окружности (СК вида, мм)")] double xc,
[Description("Y центра окружности")] double yc,
[Description("Радиус окружности, мм")] double radius,
[Description("Направление выноски, градусы")] double angle = 0,
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
{
await session.ConnectAsync();
var r = await drawing.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angle);
return $"Диаметральный размер поставлен на виде {r.ViewNumber}, Ø{r.Value:0.###} мм.";
}
}