Files
kompas3d-mcp/docs/superpowers/specs/2026-05-28-drawing-leader-design.md
T
mikhail 5d51b9e1af docs: выноска — TODO, README, ARCHITECTURE, OPEN_QUESTIONS, presentation, CLAUDE.md
Инкремент 9. 83 инструмента, 331 тест. + заметка о ревью реализации в спеке
(Codex + pi/glm-5.1 + pi/kimi-k2.6).

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

10 KiB
Raw Blame History

Дизайн: линия-выноска с текстом (drawing_add_leader) через API7

Дата: 2026-05-28 Статус: дизайн согласован, спайк проведён, ревью Codex + pi/glm-5.1 + pi/kimi-k2.6 учтено — к реализации

Правки по ревью реализации (Codex + pi/glm-5.1 + pi/kimi-k2.6)

  • Проверка возврата AddBranchByPoint/SetBranchTextPosition (оба bool) — ранняя диагностика вместо позднего RPC_E_SERVERFAULT (Codex, glm).
  • RU-алиасы в сообщении ShelfDirections.Parse и в [Description] инструмента (glm, kimi).
  • Интеграционный тест направления полки параметризован Right/Left/Up/Down — покрывает всю поверхность ToKompas на реальном COM (glm, kimi).
  • Отклонено (обоснование): unit-тест ToKompas (граница проекта — тест не ссылается на Kompas6Constants); e2e-тест tool-обёртки (тонкая, Parse+сервис покрыты); angleDeg (у ILeader/IBaseLeader нет свойства Angle — ориентация геометрическая); выравнивание default text у AddText (вне объёма; required-контракт AddLeader корректнее).

Правки по ревью спека (Codex + pi/glm-5.1 + pi/kimi-k2.6)

  • Сигнатура DrawingValidation.RequireNonEmptyText(text, nameof(text)) (двухаргументный — как есть).
  • QI с диагностикой: bl as IBranchs ?? throw, bl as ILeader ?? throw (не прямой cast); read-back leader.TextOnShelf?.Str ?? string.Empty; двухшаговый null-check Leaders (коллекция) и Add() — как AddRoughCore.
  • ShelfDirections.ToKompas(Auto) бросает ArgumentOutOfRangeException (Auto обрабатывается в сервисе ДО вызова — «не задавать ShelfDirection»; прямой вызов для Auto — ошибка). RU-алиасы Parse: авто/вправо/влево/вверх/вниз.
  • Вырожденная выноска: RequireDistinctPoints(x, y, textX, textY) (остриё ≠ полка; есть валидатор).
  • Критический порядок (комментарий в коде): AddBranchByPoint ДО SetBranchTextPosition/Update (иначе RPC_E_SERVERFAULT — выноска без ветвей).
  • Тесты: пустой текст — "" И " "; viewNumber=0 резолвится в первый вид; путь shelfDirection=auto (ShelfDirection не задаётся). XML-<summary> членов ShelfDirection — «в ЛОКАЛЬНОЙ СК вида».
  • Нейминг COM-объекта — baseLeader (не bl). GetViewLeaderCountAsync считает ВСЕ подтипы выносок (для v1 ок; фильтр — при добавлении ksDrPosLeader/… — в «Дальнейшее»).

Цель

Веха 2D-ЧЕРТЁЖ, инкремент 9. Поставить на виде линию-выноску (leader) с текстовой надписью на полке — поясняющее обозначение, указывающее стрелкой на элемент (примечание, материал детали, и т.п.).

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

Спайк (_SpikeLeader, прогнан на реальном КОМПАС, затем удалён). Ключ: только что созданная выноска имеет 0 ответвлений — любые операции над ответвлением (SetSignPosition) и Update на выноске без ветвей падают с RPC_E_SERVERFAULT; сначала нужно добавить ответвление через IBranchs.

IBaseLeader bl = symbols.Leaders.Add(DrawingObjectTypeEnum.ksDrLeader);  // ksDrLeader=20
((IBranchs)bl).AddBranchByPoint(0, x, y);   // QI; ответвление 0 — остриё (куда указывает стрелка)
bl.SetBranchTextPosition(textX, textY);      // положение полки/текста
((ILeader)bl).TextOnShelf.Str = text;        // надпись на полке (.Str — наш interop)
// опц.: ((ILeader)bl).ShelfDirection = ksShelfDirectionEnum.ksLSRight;
bl.Update();   // True, bl.Valid == True

Проверено: AddBranchByPoint(0,x,y)BranchCount=1; Update=True, Valid=True, TextOnShelf.Str round-trip; Leaders.Count +1. Работает с явной ShelfDirection и без неё. BaseLeader реализует IBaseLeader/IDrawingObject; IBranchs и ILeader — COM-QI от него.

Интерфейсы: ILeaders.Add(DrawingObjectTypeEnum)BaseLeader (для обычной выноски ksDrLeader=20); IBranchs.AddBranchByPoint(int index, double x, double y); IBaseLeader.SetBranchTextPosition(x,y) / Update / Valid / Delete / ArrowType (ksArrowEnum); ILeader.TextOnShelf (IText), ShelfDirection (ksShelfDirectionEnum: ksLSNone=0/ksLSRight=1/ksLSUp=2/ksLSDown=3/ksLSLeft=-1).

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

Инструмент Параметры Поведение
drawing_add_leader x,y (остриё — куда указывает стрелка), textX,textY (полка/якорь текста), text, shelfDirection="auto" (auto|right|left|up|down), viewNumber=0 Поставить выноску на виде: стрелка из полки указывает в (x,y), текст text на полке у (textX,textY). Координаты — ЛОКАЛЬНАЯ СК вида (мм). shelfDirection — направление полки (auto = по умолчанию КОМПАС). Возвращает текст (read-back) и номер вида.

Архитектура

В DrawingService (namespace Kompas.Mcp.Core.Drawings).

  • AddLeaderAsync(viewNumber, x, y, textX, textY, text, shelfDirection, ct)DrawingAnnotationResult (переиспользуем: Value=read-back текст, ViewNumber).
  • Использует существующий RequireSymbols2DContainer(viewNumber) (выноски в ISymbols2DContainer.Leaders).
  • Новый файл src/Kompas.Mcp.Core/Drawings/ShelfDirection.cs: enum ShelfDirection {Auto,Right,Left,Up,Down} (XML-<summary> на членах) + ShelfDirections.Parse(string) / ToKompas(ShelfDirection)→ksShelfDirectionEnum по образцу AngleDimensionTypes. Auto → не задавать ShelfDirection (оставить дефолт КОМПАС).
  • Счётчик GetViewLeaderCountAsync(viewNumber) (для теста попадания в вид).

Реализация

AddLeaderCore: RequireNonEmptyText(text) + RequireFiniteCoords(x,y,textX,textY)RequireSymbols2DContainersymbols.Leaders (null-check) → .Add(ksDrLeader) (null-check) → внутри try/catch+Delete: ((IBranchs)bl).AddBranchByPoint(0, x, y) (QI null-check), bl.SetBranchTextPosition(textX, textY), ((ILeader)bl).TextOnShelf (null-check) .Str = text, если shelfDirection != Auto: ((ILeader)bl).ShelfDirection = ShelfDirections.ToKompas(...)bl.Update() (FALSE → откат) → bl.Valid (false → откат) → read-back ((ILeader)bl).TextOnShelf.StrDrawingAnnotationResult{Value=read-back, ViewNumber}.

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

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

  • RequireNonEmptyText(text, nameof(text)), RequireFiniteCoords(x,y,textX,textY), RequireDistinctPoints(x, y, textX, textY) (остриё ≠ полка) — все уже есть.
  • ShelfDirections.Parseauto/right/left/up/down (+ рус. авто/вправо/влево/вверх/вниз, регистр/пробелы); неизвестное → ArgumentException; null → ArgumentNullException. ToKompas (→ ksShelfDirectionEnum) НЕ покрываем unit (граница проекта); бросает ArgumentOutOfRangeException на Auto.

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

Unit (ShelfDirectionsTests)

  • Parse: auto/right/left/up/down (+ регистр, рус.) → enum; неизвестное → ArgumentException; null → ArgumentNullException.

Integration (DrawingLeaderTests; модель — коробка + стандартные виды)

  1. Ставится + round-trip + viewNumber=0: text="Примечание 1", shelfDirection=auto, viewNumber=0Value=="Примечание 1" (read-back), viewNumber=0 резолвится в первый вид (r.ViewNumber совпал), Leaders.Count целевого вида +1.
  2. shelfDirection=left (не-auto путь) на целевом виде → ставится, Leaders.Count +1.
  3. Пустой текст ("" и " ") → ArgumentException (до Add).
  4. Вырожденная (остриё совпадает с полкой) → ArgumentException (до Add).
  5. Нет видов → понятная ошибка. 6. Несуществующий viewNumber>0 → понятная ошибка.

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

  • Многострочный текст полки / текст под полкой (TextUnderShelf); несколько ответвлений (AddBranchByPoint с разными индексами); привязка к геометрии (IBranchs.SetBaseObject); тип стрелки (ArrowType); специальные выноски (позиция/клеймо/маркер: ksDrPosLeader/ksDrBrandLeader/ksDrMarkerLeader). Базы (Bases), допуски формы (Tolerances).