Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-drawing-title-block-design.md
mikhail eaa4a56708 feat: drawing_fill_title_block — упрочнение по ревью Codex реализации
#3/#6: доступ к графе через RequireStampCell(stamp,columnId) — null-check ячейки
+ единое место валидации (запись и чтение). #4: проверка columnId>0 в helper
(защищает и ReadStampCellAsync). #2/#5: нетранзакционность записи зафиксирована
комментарием (откат при Update()==FALSE спекулятивен, не делаем). RCW — долг v2-2.

Тесты без изменений в числе: 123 unit + 65 integration = 188 зелёных.

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

9.7 KiB

Дизайн: основная надпись чертежа (drawing_fill_title_block) через API7

Дата: 2026-05-27 Статус: реализовано и проверено (4 интеграционных + 7 unit, всего 188); ревью Codex спека и реализации учтено

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

  • null-check ячейки (#3): доступ к графе через RequireStampCell(stamp, columnId) — проверяет columnId > 0 и Text[columnId] != null с диагностикой; используется в записи и чтении (единое место валидации, #6).
  • columnId <= 0 (#4): валидируется в RequireStampCell (защищает и ReadStampCellAsync).
  • Нетранзакционность (#2/#5): записываем все графы, затем один Update(); при Update()==FALSE откат старых значений не делаем — зафиксировано комментарием как осознанное поведение (откат спекулятивен, сценарий невоспроизводим).
  • RCW — осознанный долг (v2-2), точечный release не вводим.

Находка спайка: Str ПЕРЕЗАПИСЫВАЕТ (Clear не нужен)

Двойная запись в графу 2 («ДЕТАЛЬ.001» → «ИЗДЕЛИЕ.002») → чтение даёт «ИЗДЕЛИЕ.002» (не дописано). Значит stamp.Text[id].Str = value замещает содержимое — IText.Clear() перед записью НЕ нужен.

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

  • Update() результат проверять: if (!stamp.Update()) throw.
  • null-checks: LayoutSheets, sheet, sheet.Stamp (ранняя диагностируемая ошибка).
  • RequireActiveDrawing() сохраняет проверку doc is not IKompasDocument2D (не только DocumentType); RequireActiveDrawingViews становится надстройкой над ним.
  • ColumnId switch — дефолт ArgumentOutOfRangeException; unit-тест на (StampField)999.
  • Тест: писать и перечитывать ВСЕ три графы (вкл. name=графа 1); явный reread Text[id].Str; тест overwrite (запись дважды → читается второе) — уже подтверждён спайком, перенесём в регрессию.
  • name = «Наименование изделия» (графа 1, ksStPartNumber) — однозначно в описании (не путать с ksStDocumentName=51).
  • Счётчик результата = число непустых (trimmed) полей, записанных до успешного Update().
  • Сигнатура: FillTitleBlockAsync(string? designation = null, string? name = null, string? material = null, ct).
  • Scale (графа 6) и прочие — ЯВНО вне scope инкремента 2. Spike-метод и spike-тест удаляются.
  • RCW точечно не освобождаем (v2-2).

Цель

Веха 2D-ЧЕРТЁЖ, инкремент 2 (оформление). После стандартных видов (drawing_create_standard_views) — заполнить основную надпись (штамп, title block) чертежа: обозначение, наименование, материал. Чертёж с видами + заполненным штампом = презентабельный конструкторский документ. Размеры на видах — отдельный инкремент 3 (нужен спайк по размерным интерфейсам).

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

Спайк (DrawingStampSpikeTests, зелёный) подтвердил путь API7 — round-trip графы:

IKompasDocument2D doc2d = (IKompasDocument2D)app.ActiveDocument;        // активный чертёж
ILayoutSheet sheet = (ILayoutSheet)doc2d.LayoutSheets.ItemByNumber[1];  // лист 1 (1-based)
IStamp stamp = (IStamp)sheet.Stamp;
stamp.Text[columnId].Str = text;   // Text[Int32 Id] — индексированное свойство; Id = номер графы
stamp.Update();                    // фиксирует
string readBack = stamp.Text[columnId].Str;  // чтение для проверки

Проверено: запись в графу 2 (обозначение) «ДЕТАЛЬ.001» → чтение возвращает «ДЕТАЛЬ.001». IText.Str — read/write; IStamp.Text[Id] — индексированное свойство (рефлексия: get_Text(Int32 Id)). Update() фиксирует; перестроение документа не нужно.

Номера граф ГОСТ (форма 1), ksStampEnum: ksStPartNumber=1 (наименование изделия), ksStDescription=2 (обозначение документа), ksStMaterial=3 (материал), ksStScale=6 (масштаб), ksStAuthor=110 (разработал) и т.д.

Архитектура

Метод в существующем DrawingService. Свой enum StampField (паттерн BasePlane/MateType) маппится на номера граф — COM-числа не текут в Host/тесты.

  • src/Kompas.Mcp.Core/Drawings/StampField.cs — enum StampField + static StampFields (ColumnId(field) → номер графы).
  • DrawingService.FillTitleBlockAsync(designation?, name?, material?, ct) — реализация.
  • Инструмент drawing_fill_title_block в DrawingTools.

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

Инструмент Параметры Поведение
drawing_fill_title_block designation?, name?, material? (все опц.) Заполнить графы основной надписи активного чертежа: обозначение (графа 2), наименование (1), материал (3). Передаются только нужные поля (null/пусто — пропускается). Возвращает число заполненных граф.

Контракт: активный документ — чертёж; хотя бы одно поле задано (иначе ошибка). Набор полей — 3 ключевые графы ГОСТ (расширяемо). За один вызов заполняются все переданные (меньше round-trip'ов для LLM-агента, чем у «одна графа за вызов»).

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

  • StampFields.ColumnId(StampField)Designation→2, Name→1, Material→3 (unit-тест маппинга).
  • В сервисе: собрать непустые (после Trim) поля; если ни одного — ArgumentException «укажите хотя бы одно поле». (Чистый помощник StampFields.Collect(...) → список (columnId,text) — unit-тестируемо без COM.)

Проверка активного документа

RequireActiveDrawing()IKompasDocument2D (тип ksDocumentDrawing, иначе раздельная ошибка) — выделить из существующего RequireActiveDrawingViews общий шаг (тот станет надстройкой: вернуть .ViewsAndLayersManager.Views).

Реализация

FillTitleBlockCore: собрать поля (StampFields.Collect) → если пусто, ошибка → RequireActiveDrawingsheet = LayoutSheets.ItemByNumber[1]stamp = (IStamp)sheet.Stamp → для каждого поля stamp.Text[columnId].Str = textstamp.Update() (результат проверить) → вернуть число граф. Транзитные RCW не освобождаем точечно (долг v2-2, как везде).

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

Unit (StampFieldsTests)

  • StampFields.ColumnId — каждое поле → ожидаемый номер графы (2/1/3).
  • StampFields.Collect — пропускает null/пусто/пробелы; при всех пустых даёт пустой список; собирает заданные с обрезкой пробелов.

Integration (DrawingStampTests)

Паттерн: создать чертёж → FillTitleBlockAsync(...) → перечитать графы напрямую (helper в сервисе ReadStampCellAsync(columnId) для теста ИЛИ проверка через возврат) → finally CloseAsync (база IntegrationTestBase).

  1. Заполнение и чтение: designation="ДЕТАЛЬ.001", material="Сталь 45" → 2 графы; чтение графы 2 == «ДЕТАЛЬ.001», графы 3 == «Сталь 45».
  2. Пустой ввод: все поля null → ArgumentException (ни одной графы не тронуто).
  3. Не-чертёж: активный документ — деталь → ошибка.

(Для чтения в тесте — добавить DrawingService.ReadStampCellAsync(columnId) — он же подтверждает round-trip; спайк-файл удаляется.)

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

  • Прочие графы (масштаб, разработал, даты, литера), размеры/обозначения на видах (инкремент 3), рамка/формат листа.