# Дизайн: основная надпись чертежа (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`) → если пусто, ошибка → `RequireActiveDrawing`
→ `sheet = LayoutSheets.ItemByNumber[1]` → `stamp = (IStamp)sheet.Stamp` → для каждого поля
`stamp.Text[columnId].Str = text` → `stamp.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),
  рамка/формат листа.
