Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md
T
mikhail 246233727c feat(drawing): радиальный (R) и угловой размеры на виде чертежа
drawing_add_radial_dimension (R окружности/дуги; NominalValue = радиус) и
drawing_add_angular_dimension (угол между двумя сторонами; angleType min/max/more
выбирает измеряемый сектор) через API7 ISymbols2DContainer. Завершают базовое
семейство размеров чертежа (после линейного и диаметрального).

- DrawingService: AddRadialDimensionAsync/AddAngularDimensionAsync + счётчики
- AngleDimensionType (enum + Parse/ToKompas, по образцу DimensionOrientations)
- DrawingDimensionResult.Value — единице-нейтральный (мм / градусы)
- 9 unit + 13 интеграционных тестов (всего 240 зелёных)

Спайк подтвердил: радиальный NominalValue = РАДИУС (не диаметр, вопреки справке);
у углового измеряемый угол выбирает angleType (min=90°/more=270°), а (x3,y3) лишь
позиционирует дугу. Спек и ревью Codex — docs/superpowers/specs/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:51:10 +03:00

14 KiB
Raw Blame History

Дизайн: радиальный (R) и угловой размеры на виде чертежа через API7

Дата: 2026-05-27 Статус: реализовано и проверено (5 радиальных + 8 угловых интеграционных + 9 unit; всего 240 тестов зелёных); ревью Codex спека учтено

Уточнение по реализации (важно)

Спайк изначально решил, что измеряемый сектор выбирает положение дуги (x3,y3). При реализации выяснилось иначе: сектор (90° vs 270°) выбирает angleType (ksADMinAngle/MaxAngle/MoreAngle), а (x3,y3) лишь позиционирует размерную дугу и задаёт её радиус. (270° в спайке возникал потому, что X3/Y3 не были заданы = совпадали с вершиной → вырождение.) Проверено: angleType=Min→90°, angleType=More→270° на одной геометрии. Тест #8 и описания инструмента/сервиса исправлены под это.

Правки по ревью Codex (спек)

  • #1 угловой: добавлен RequireDistinctPoints(x1,y1,x2,y2) (совпавшие точки сторон → одинаковые лучи → вырождение ещё до Add).
  • #3 добавлен тест отката после Add: коллинеарные сонаправленные лучи (вершина(0,0),(10,0),(20,0)) → угол ≈0 → пост-проверка value<=0 → откат Delete + ошибка; счётчик угловых не изменился.
  • #2 поведение x3,y3 (выбор сектора) задокументировано в описании инструмента; добавлен интеграционный тест выбора сектора (та же геометрия, x3,y3 в дополнительном секторе → ~270°). Большой/тупой угол НЕ отвергаем — это легитимный результат (рефлексный угол через more/положение дуги).
  • #4 добавлены тесты на несуществующий viewNumber>0 (ветка FindView «вид не найден») для обоих.
  • #5 DrawingDimensionResult.Value — XML-док станет единице-нейтральным («значение размера»); ответ drawing_add_angular_dimension форматируется в градусах (°), не «мм».
  • #6 радиальный использует DimensionAngles.ToRadians(angleDeg) (как диаметральный), не ручное ·π/180.

Цель

Веха 2D-ЧЕРТЁЖ, инкремент 5. После линейного и диаметрального размеров — радиальный (R) и угловой размеры на виде. Завершают базовое семейство размеров чертежа. Оба переиспользуют проверенный путь ISymbols2DContainer (QI от IView) и паттерн «Add → set → Update → проверка Valid → чтение NominalValue → откат Delete при ошибке».

Спайк: поведение подтверждено вживую (НЕ разучивать)

Спайк (_SpikeDimensions, прогнан на реальном КОМПАС, затем удалён) на коробке 40×30×20 с тремя стандартными видами. Контейнер размеров — (ISymbols2DContainer)view.

Радиальный

IRadialDimension rd = symbols.RadialDimensions.Add();
rd.Xc = 10; rd.Yc = 10; rd.Radius = 15; rd.Angle = π/4;   // Angle — РАДИАНЫ (направление выноски)
rd.DimensionType = true;                                   // BOOL: на значение НЕ влияет (стиль)
((IDimensionText)rd).AutoNominalValue = true;
rd.Update();  // True, rd.Valid == True
double value = ((IDimensionText)rd).NominalValue;          // == 15 — РАДИУС (не диаметр!)

Ключевой факт (расходится со справкой SDK): у радиального NominalValue возвращает радиус (Radius=15 → NominalValue=15), а НЕ диаметр. Проверено для DimensionType и true, и false — оба Valid, значение одинаковое (BOOL влияет лишь на стиль «от центра / нет», не на величину). Координаты — локальная СК вида, мм. Размер «свободный» (по Xc/Yc/Radius, без BaseObject).

Угловой

IAngleDimension ad = symbols.AngleDimensions.Add(DrawingObjectTypeEnum.ksDrADimension);  // =10
ad.Xc = 0; ad.Yc = 0;        // вершина угла
ad.X1 = 10; ad.Y1 = 0;       // точка на стороне 1  → луч (Xc,Yc)→(X1,Y1)
ad.X2 = 0;  ad.Y2 = 10;      // точка на стороне 2  → луч (Xc,Yc)→(X2,Y2)
ad.X3 = 5;  ad.Y3 = 5;       // положение размерной дуги — ЗАДАЁТ ИЗМЕРЯЕМЫЙ СЕКТОР + радиус дуги
ad.DimensionType = ksADMinAngle;  // 0=min / 1=max / 2=more180
((IDimensionText)ad).AutoNominalValue = true;
ad.Update();  // True, ad.Valid == True
double value = ((IDimensionText)ad).NominalValue;          // == 90 — ГРАДУСЫ

Проверено:

  • вершина(0,0), точки (10,0)&(0,10), X3/Y3=(5,5) → 90° ✓;
  • вершина(0,0), точки (10,0)&(10,10), X3/Y3=(8,4) → 45° ✓.
  • X3/Y3 критичен: без него тот же угол измерился как 270° (дополнительный сектор). Точка положения дуги задаёт, какой из двух секторов меряется; заодно радиус дуги пересчитывается в dist(вершина, X3/Y3) (заданный Radius перетирается → его не экспонируем).
  • Подход через Angle1/Angle2 (углы наклона сторон) — вырожденный (точки схлопываются к вершине, X1≈0.001), не используем. Геометрию ведут только координаты точек.
  • NominalValue — в градусах.

Add(DrawingObjectTypeEnum) требует код типа: ksDrADimension = 10 (обычный угловой), ksDrABreakDimension = 39 (с обрывом — вне объёма).

MCP-инструменты

Инструмент Параметры Поведение
drawing_add_radial_dimension xc,yc (центр окружности), radius, angle=0 (°), viewNumber=0 Радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом radius (ЛСК вида, мм). angle — направление выноски в ГРАДУСАХ (внутри → радианы). viewNumber: 0 = первый/главный. Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида.
drawing_add_angular_dimension xc,yc (вершина), x1,y1 (точка на стороне 1), x2,y2 (точка на стороне 2), x3,y3 (положение размерной дуги — задаёт сектор), angleType="min", viewNumber=0 Угловой размер между лучами (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) (ЛСК вида, мм). x3,y3 должны лежать в измеряемом секторе. angleType: min/max/more. Значение (градусы) измеряется автоматически. Возвращает угол (°) и номер вида.

Углы выноски (angle) экспонируются в градусах (LLM-дружелюбно), внутри ·π/180.

Архитектура

В существующем DrawingService (рядом с AddDiametralDimensionAsync); переиспользуем RequireSymbols2DContainer(viewNumber), DrawingDimensionResult, паттерн отката.

  • DrawingService.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angleDeg, ct).
  • DrawingService.AddAngularDimensionAsync(viewNumber, xc, yc, x1, y1, x2, y2, x3, y3, angleType, ct).
  • Инструменты drawing_add_radial_dimension, drawing_add_angular_dimension в DrawingTools.
  • Helpers GetViewRadialDimensionCountAsync, GetViewAngularDimensionCountAsync (для тестов — попадание в вид; размеры в ISymbols2DContainer.RadialDimensions/AngleDimensions.Count, не в IView.ObjectCount).
  • Новый файл src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs: enum AngleDimensionType {Min,Max,More}
    • static AngleDimensionTypes.Parse(string) / ToKompas(...) (→ ksADMinAngle/ksADMaxAngle/ksADMoreAngle), по образцу DimensionOrientations.

DrawingDimensionResult (Value + ViewNumber) переиспользуется: радиальный Value=радиус, угловой Value=градусы.

Валидация (чистые static, unit-тест)

  • Радиальный: RequireFiniteCoords(xc, yc, angleDeg) + RequirePositiveRadius(radius) — уже есть.
  • Угловой: RequireFiniteCoords(xc,yc,x1,y1,x2,y2,x3,y3) (8 координат) + RequireDistinctPoints четырежды: вершина≠(x1,y1), вершина≠(x2,y2), вершина≠(x3,y3) (иначе дуга нулевого радиуса), (x1,y1)≠(x2,y2) (#1: совпавшие точки сторон → одинаковые лучи). Все уже есть; новых валидаторов не требуется.

Реализация

AddRadialDimensionCore (зеркало диаметрального, но значение = радиус): валидация → RequireSymbols2DContainerRadialDimensions.Add() (null-check) → Xc/Yc/Radius, Angle=DimensionAngles.ToRadians(angleDeg) (#6), DimensionType=true (стиль R от центра, явно), AutoNominalValue=trueUpdate() (FALSE → откат Delete+ошибка) → Valid (false → откат) → value = NominalValue (радиус); value<=0 || !IsFinite → откат+ошибка → DrawingDimensionResult{Value, ViewNumber}.

AddAngularDimensionCore: валидация → RequireSymbols2DContainerAngleDimensions.Add(ksDrADimension) (null-check) → Xc/Yc/X1/Y1/X2/Y2/X3/Y3, DimensionType=AngleDimensionTypes.ToKompas(angleType), AutoNominalValue=trueUpdate() (FALSE → откат) → Valid (false → откат) → value=NominalValue (°); value<=0 || !IsFinite → откат+ошибка с подсказкой (вырожденный угол: стороны коллинеарны или x3,y3 вне сектора) → DrawingDimensionResult{Value, ViewNumber}. Угол в (0,360); защиту по верхней границе не форсируем (КОМПАС нормализует, more может дать >180).

RCW точечно не освобождаем (консистентно с линейным/диаметральным; долг v2-2).

Тестирование

Unit (DrawingDimensionTypeTests — новый, либо в DrawingValidationTests)

  • AngleDimensionTypes.Parse: min/max/more (+ рус. синонимы) → enum; неизвестное → ArgumentException.
  • AngleDimensionTypes.ToKompas: маппинг трёх значений → ksADMinAngle/ksADMaxAngle/ksADMoreAngle.
  • (ToRadians, RequirePositiveRadius, координаты — уже покрыты.)

Integration (DrawingRadialTests, DrawingAngularTests; наследуют IntegrationTestBase)

Радиальный:

  1. Значение = радиус: radius=12 → Value ≈ 12 (промотированный спайк).
  2. Попадание в вид: RadialDimensions.Count целевого вида +1 (адресация по viewNumber).
  3. Радиус ≤ 0ArgumentOutOfRangeException (до Add).
  4. Нет видов → понятная ошибка.

Угловой: 5. Измеряет 90°: вершина(0,0), (10,0)&(0,10), x3/y3=(5,5) → Value ≈ 90. 6. Измеряет 45°: вершина(0,0), (10,0)&(10,10), x3/y3=(8,4) → Value ≈ 45. 7. Попадание в вид: AngleDimensions.Count целевого вида +1. 8. Выбор угла типом (#2): та же геометрия 90°, но angleType=MoreValue ≈ 270 (документированное поведение: какой из двух углов измерять, выбирает angleType, не x3,y3). 9. Вырожденный угол до Add (вершина совпадает с точкой стороны) → ArgumentException. 10. Откат после Add (#3): коллинеарные сонаправленные лучи (0,0),(10,0),(20,0) → угол ≈0 → InvalidOperationException; AngleDimensions.Count целевого вида НЕ изменился (откат Delete). 11. Несуществующий viewNumber>0 (#4) → понятная ошибка (ветка FindView). 12. Нет видов → понятная ошибка.

(Радиальный — аналогично добавлен тест на несуществующий viewNumber>0, #4.)

Дальнейшее (вне спека)

  • Ассоциативная привязка размеров к геометрии вида (BaseObject/BaseObject1/2); рамка/формат листа; текстовые обозначения (шероховатость Roughs, допуски формы Tolerances, выноски Leaders, тех. требования); радиальный с изломом (BreakRadialDimensions), угловой с обрывом (ksDrABreakDimension).