742a97177b
Веха 2D-ЧЕРТЁЖ, инкремент 2 (оформление). DrawingService.FillTitleBlockAsync: LayoutSheets.ItemByNumber[1].Stamp.Text[id].Str = text → Update(). Enum StampField → номер графы ГОСТ (designation=2, name=1, material=3). Заполнение нескольких граф за вызов; перезапись (Str замещает, Clear не нужен — подтверждено спайком). Новый инструмент drawing_fill_title_block; ReadStampCellAsync для проверки. Ревью Codex спека учтено: Update() результат проверяется; null-checks (LayoutSheets/sheet/Stamp); RequireActiveDrawing выделен (сохраняет cast-проверку); ColumnId switch с дефолтным throw; счётчик = непустые поля; name=графа 1 однозначно. Валидация — чистый StampFields (ColumnId + Collect). Тесты: +7 unit (StampFields), +4 integration (DrawingStampTests: заполнение+reread 3 граф, перезапись, пустой ввод, не-чертёж). Итого 123 unit + 65 integration = 188. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
119 lines
8.7 KiB
Markdown
119 lines
8.7 KiB
Markdown
# Дизайн: основная надпись чертежа (drawing_fill_title_block) через API7
|
||
|
||
**Дата:** 2026-05-27
|
||
**Статус:** реализовано и проверено (4 интеграционных + 7 unit, всего 188); ревью Codex спека учтено
|
||
|
||
## Находка спайка: `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),
|
||
рамка/формат листа.
|