Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-drawing-associative-dimension-design.md
T
mikhail 775128484e feat(drawing): ассоциативная привязка диаметрального/радиального размера к окружности вида
Флаг associate у drawing_add_diametral_dimension/drawing_add_radial_dimension:
- associate=false (по умолчанию) — прежний свободный размер (без изменений);
- associate=true — (xc,yc,radius) как ключ поиска спроецированной окружности
  (IDrawingContainer.Circles), привязка через BaseObject; значение Ø/R читается
  из геометрии и обновляется с моделью.

- CircularObjectMatch.SelectMatchIndex (чистый отбор по центру+радиусу, допуск 1 мм;
  бросает при отсутствии/неоднозначности; концентрические разделяются радиусом)
- RequireViewContainers (вид → ISymbols2DContainer + IDrawingContainer), CollectCircles, FindBaseCircle
- 5 unit + 6 интеграционных тестов (всего 278 зелёных), сборка Release чистая

Спайк подтвердил: BaseObject=circle без задания Xc/Yc/Radius → Valid, NominalValue из
геометрии (Ø=20/R=10 для R10). Объём — только окружности (Circles); дуги/угловой/линейный —
далее. Ревью Codex спека учтено (оба контейнера, guard вокруг блока, доказательство
ассоциативности тестом radius=10.3→Ø20, регресс обоих свободных путей).

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

12 KiB
Raw Blame History

Дизайн: ассоциативная привязка диаметрального и радиального размеров к геометрии вида (API7)

Дата: 2026-05-27 Статус: дизайн согласован (вариант A — флаг associate), спайк проведён, ревью Codex спека учтено — к реализации

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

  • #3/#4 объём сужен до окружностей (IDrawingContainer.Circles): спайк подтвердил привязку только к окружности. Дуги (Arcs) — следующий инкремент со своим спайком (привязка IArc как BaseObject для диам./радиального не проверена; дуги одного родителя неотличимы по центру+радиусу).
  • #1 вид резолвится один раз и приводится к обоим контейнерам: ISymbols2DContainer (создание размера) И IDrawingContainer (поиск окружности). Новый helper RequireViewContainers(viewNumber)(IView, ISymbols2DContainer, IDrawingContainer).
  • #2 весь блок после …Dimensions.Add() (присвоение BaseObject/Angle/AutoNominalValue, Update, Valid, read-back) — внутри того же try/catch с Delete при любом сбое (как свободный путь).
  • #5 тест доказывает ассоциативность: ключ поиска radius=10.3 (в допуске находит окружность R10) → associate вернёт Ø=20 (из геометрии), свободный дал бы 2·10.3=20.6; Value≈20 (не 20.6) доказывает чтение из геометрии. + диагностический read-back BaseObject != null.
  • #6 обе ветки (свободная/associate) конвертируют angle через DimensionAngles.ToRadians(angleDeg) перед присвоением dim.Angle.
  • #7 пост-инвариант значения (value > 0 && double.IsFinite) сохраняется и в associate-ветке (иначе откат).
  • #8 регресс-тесты свободного пути (associate=false) для ОБОИХ — диаметрального и радиального.

Цель

Веха 2D-ЧЕРТЁЖ, инкремент 7. Размеры пока «свободные» (значение из заданных координат). Ассоциативная привязка делает диаметральный/радиальный размер связанным с проекцией геометрии: значение (Ø/R) читается из спроецированной окружности/дуги вида через BaseObject и обновляется вместе с моделью. Первый шаг к «умному» чертежу.

Объём (обоснован рефлексией interop)

Только диаметральный и радиальный, привязка только к окружности (IDrawingContainer.Circles) — у IDiametralDimension/IRadialDimension есть свойство BaseObject; _Circle реализует IDrawingObject, поэтому объект из коллекции присваивается dim.BaseObject (подтверждено спайком). У ILineDimension BaseObject НЕТ; угловой требует BaseObject1/2; привязка к дуге (Arcs) не проверена — всё это отдельные инкременты (вне объёма).

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

Спайк (_SpikeAssocDim, прогнан на реальном КОМПАС, затем удалён): цилиндр (окружность R10, выдавлен на 20) → стандартные виды. Один из видов содержит спроецированную окружность.

IDrawingContainer dc = (IDrawingContainer)view;   // QI от вида (как ISymbols2DContainer)
ICircles circles = dc.Circles;                     // Count=1; circles[0] as ICircle → Xc=0,Yc=0,R=10
IDiametralDimension dia = symbols.DiametralDimensions.Add();
dia.BaseObject = circles[0];   // IDrawingObject; Xc/Yc/Radius НЕ задаём
dia.Angle = π/4;               // направление выноски (размещение)
dia.Update();   // True, Valid=True
double v = ((IDimensionText)dia).NominalValue;     // == 20 — ДИАМЕТР, прочитан из геометрии!

Проверено: BaseObject=circle без задания координат → Update=True, Valid=True, диаметральный NominalValue=20 (=2·R геометрии), радиальный NominalValue=10 (=R). BaseObject остаётся непустым после. Геометрия вида: IDrawingContainer.Circles (ICircle: Xc/Yc/Radius), .Arcs (IArc: Xc/Yc/Radius/Angle1/Angle2), .LineSegments. Нет «find by point» — выбор перебором коллекции.

MCP-инструменты (расширение существующих, вариант A)

К drawing_add_diametral_dimension и drawing_add_radial_dimension добавляется параметр associate=false:

associate Поведение
false (по умолчанию) Текущее «свободное»: Xc/Yc/Radius задаются напрямую (без изменений, обратная совместимость).
true (xc,yc,radius)ключ поиска: найти в виде окружность/дугу с центром ≈ (xc,yc) и радиусом ≈ radius, привязать BaseObject; значение (Ø/R) читается из геометрии.

Координаты ключа — view-local (= координаты модели при масштабе 1:1; при ином масштабе — масштабированные, как у прочих размеров). angle — направление выноски (°), как сейчас. viewNumber — как сейчас.

Выбор объекта

В IDrawingContainer нет «find by point». Перебираем Circles, собираем кандидатов (Xc, Yc, Radius), ищем подходящий по ключу (xc, yc, radius):

  • кандидат подходит, если dist(центр, ключ) ≤ tol И |R radius| ≤ tol (tol = 1.0 мм, константа);
  • ровно 1 подходящий → привязываем; 0 → ошибка «окружность не найдена рядом с (xc,yc) R≈radius»;

    1 → ошибка «неоднозначно — уточните координаты/радиус».

Радиус в ключе снимает неоднозначность концентрических окружностей (цековка R5/R10 — ключ R10 выберет только внешнюю). Логика отбора — чистая (unit-тест), перечисление COM — в сервисе.

Архитектура

  • DrawingService.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angleDeg, associate, ct) и AddRadialDimensionAsync(..., associate, ct) — добавлен associate (по умолчанию false). При false — текущая ветка без изменений. Обе ветки: angleRad = DimensionAngles.ToRadians(angleDeg).
  • При associate=true: RequireViewContainers(viewNumber) → собрать кандидатов из Circles (CollectCircles) → CircularObjectMatch.SelectMatchIndex(...) → взять IDrawingObject по индексу → symbols.{Diametral|Radial}Dimensions.Add()внутри try/catch+Delete: dim.BaseObject = obj, dim.Angle = angleRad, AutoNominalValue=trueUpdate() (FALSE→откат) → Valid (false→откат) → value = NominalValue (из геометрии); value<=0 || !IsFinite → откат → DrawingDimensionResult.
  • Новый helper RequireViewContainers(viewNumber)(IView View, ISymbols2DContainer Symbols, IDrawingContainer Drawing) — резолвит вид один раз, оба QI с раздельной диагностикой (переиспользует FindView).
  • Новый файл src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs: чистый статический record CircleCandidate(double Xc, double Yc, double Radius) + int SelectMatchIndex(IReadOnlyList<CircleCandidate> candidates, double keyX, double keyY, double keyR, double tol) — возвращает индекс единственного совпадения; InvalidOperationException при 0 (нет) / >1 (неоднозначно).
  • Сбор кандидатов из COM (Circles в список с сохранением ссылки на IDrawingObject + геометрии) — в сервисе (private helper CollectCircles(IDrawingContainer)List<(IDrawingObject Obj, CircleCandidate Geom)>).
  • DrawingDimensionResult переиспользуется (значение из геометрии). Свободный путь не трогаем.

Валидация

  • Оба пути: RequireFiniteCoords(xc, yc, angleDeg) + RequirePositiveRadius(radius) (есть). В associate-режиме radius — положительный ключ поиска.
  • CircularObjectMatch.SelectMatchIndex — чистая логика отбора (unit).

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

Unit (CircularObjectMatchTests)

  • Единственное совпадение по центру+радиусу → индекс.
  • Нет кандидатов в допуске → InvalidOperationException (нет).
  • Два совпадения в допуске → InvalidOperationException (неоднозначно).
  • Концентрические (один центр, разный R): ключ по R выбирает нужный (не неоднозначно).
  • Пустой список → InvalidOperationException.

Integration (DrawingAssociativeDimensionTests; модель — цилиндр R10, как в спайке)

  1. Диаметральный associative + доказательство геометрии: ключ (0,0,10.3) (radius в допуске находит окружность R10) → Value ≈ 20 (Ø из геометрии, НЕ 2·10.3=20.6 — доказывает чтение из геометрии); попадание в вид (DiametralDimensions.Count +1).
  2. Радиальный associative: (0,0,10.3)Value ≈ 10 (R из геометрии, не 10.3).
  3. Нет окружности рядом: ключ далеко от геометрии → InvalidOperationException; счётчик не вырос (откат).
  4. Свободный путь не сломан — диаметральный associate=false (xc,yc,15)Value ≈ 30.
  5. Свободный путь не сломан — радиальный associate=false (xc,yc,12)Value ≈ 12 (регресс обоих).
  6. Нет видов (associate) → понятная ошибка.

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

  • Ассоциативная привязка углового (BaseObject1/2 — пара отрезков) и шероховатости (IRough.BaseObject); линейный — BaseObject отсутствует (нужен иной механизм). Рамка/формат листа; выноски/базы/допуски формы.