Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md
T
mikhail 86a5a52347 review(drawing): правки по ревью реализации Codex (угловой размер)
- описание (x3,y3) и сообщение об ошибке: угол выбирает angleType, не точка дуги
- тест angleType=Max (лучи 0°/45° → 135° тупой супплемент): min=45/max=135/more=315
- спек дополнен находкой о трёх типах угла

241 тест зелёный, сборка Release чистая.

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

16 KiB
Raw Blame History

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

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

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

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

Три типа дают три разных угла (проверено на лучах 0° и 45°):

  • Min (ksADMinAngle) → 45° (острый);
  • Max (ksADMaxAngle) → 135° (тупой супплемент, 180−45);
  • More (ksADMoreAngle) → 315° (рефлексный, 360−45).

Тесты (#8 More→270 на 90°, Max→135 на 45°) и описания инструмента/сервиса исправлены под это.

Правки по ревью Codex (реализация)

  • #1 описание параметра x3,y3 в инструменте переформулировано (точка позиционирует дугу; угол выбирает angleType, не она).
  • #2 сообщение пост-проверки углового больше не винит (x3,y3) — указывает на коллинеарность сторон.
  • #3 добавлен интеграционный тест angleType=Max (лучи 0°/45° → 135°) — покрыл маппинг ksADMaxAngle, отличный от Min/More.
  • #4 (откат радиального не тестируется) — отклонено с обоснованием: как у диаметрального (radius>0 → всегда Valid, value=radius>0; детерминированного пост-Add сбоя нет), код отката структурно идентичен протестированному угловому (коллинеарный путь, счётчик не меняется).

Правки по ревью 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): angleType=More на геометрии 90° → Value ≈ 270; angleType=Max на лучах 0°/45° → Value ≈ 135 (тупой супплемент). Какой угол измерять, выбирает 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).