Merge feat/drawing-title-block: основная надпись чертежа (оформление, инкремент 2)

drawing_fill_title_block — заполнение граф штампа (обозначение/наименование/
материал) активного чертежа через API7 (IStamp.Text[id].Str + Update).
74 инструмента, 188 тестов (123 unit + 65 integration). Спек + ревью Codex
(спека и реализации) учтены.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 17:31:31 +03:00
11 changed files with 406 additions and 25 deletions
+3 -3
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -39,7 +39,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
}
```
## Инструменты (73 инструмента)
## Инструменты (74 инструмента)
| Группа | Инструменты |
|---|---|
@@ -54,7 +54,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
| Variables | `create_variable`, `set_variable`, `delete_variable` |
| Conversion | `import_step`, `export_step` |
| Assembly | `assembly_add_component`, `assembly_add_mate` |
| Drawing | `drawing_create_standard_views` |
| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block` |
| Validation | `validate_part` |
### Типовой сценарий
@@ -73,7 +73,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
```
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции, конвертация, снимок, инспекция модели
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов
tests/Kompas.Mcp.Tests/ 177 тестов: unit + integration (integration требуют КОМПАС)
tests/Kompas.Mcp.Tests/ 188 тестов: unit + integration (integration требуют КОМПАС)
libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common)
docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/)
usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d
+6 -5
View File
@@ -1,6 +1,6 @@
# Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант)
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-D start «переменные»+package-E «offset plane»+`hole`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`+`assembly_add_mate`+`drawing_create_standard_views` (начало вехи «2D-чертёж»)). Актуализировано по коду.
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-D start «переменные»+package-E «offset plane»+`hole`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`+`assembly_add_mate`+`drawing_create_standard_views`+`drawing_fill_title_block` (веха «2D-чертёж», инкремент 2)). Актуализировано по коду.
> Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md).
> Имена интерфейсов и перечислений сверены со справкой SDK по MD-базе знаний `docs/Kompas3D_SDK/` (навык `kompas-sdk-research`).
@@ -113,7 +113,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|---|---|
| **Kompas.Mcp.Host** | Точка входа. Сборка MCP-сервера (stdio), DI (в т.ч. `ConversionService`), логи в stderr, старт и владение STA-потоком, graceful shutdown. Определения инструментов по категориям (System / Documents / Sketch / Features / Query / Conversion). **Тонкий слой**: валидация аргументов → постановка задачи на STA-поток → форматирование результата/ошибки в MCP-ответ. |
| **Kompas.Mcp.Core** | COM-слой. Менеджер соединения, менеджер документов, построитель эскизов и операций, `ConversionService` (STEP импорт/экспорт), `QueryService` (МЦХ, грани, рёбра, компоненты), `ModelInspectionService` (дерево операций, тела, переменные, drill-down, измерения), `AssemblyService` (вставка компонентов в сборку, namespace `Kompas.Mcp.Core.Assemblies`), `DrawingService` (стандартные виды чертежа, namespace `Kompas.Mcp.Core.Drawings`), снимок. Все паттерны API5/API7 живут здесь. |
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 177 тестов (116 unit + 61 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. |
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 188 тестов (123 unit + 65 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. |
Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
@@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async)
---
## 5. Карта инструментов (73 инструмента)
## 5. Карта инструментов (74 инструмента)
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`.
@@ -210,6 +210,7 @@ MCP client ──stdio──> Host (поток-пул, async)
**Drawing — 2D-чертёж (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`)**
- `drawing_create_standard_views(partFilePath, scale, x, y)` — создать стандартные ассоциативные виды (спереди/сверху/слева, `ksPtFront/ksPtUp/ksPtLeft`) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже. Реализовано через API7 `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, dx=20, dy=20)``object[]` передаётся как SAFEARRAY VT_I4. Проверки: `created==3` строго; непустота — сумма `IView.ObjectCount` новых видов (по разнице `IView.Number`) `>0`, иначе ошибка + откат (`IView.Delete`). Возвращает `DrawingViewsResult{Created,Total}`. Модель должна быть сохранена до вызова.
- `drawing_fill_title_block(designation?, name?, material?)` — заполнить графы основной надписи (штампа) активного чертежа. `designation` = обозначение документа (графа 2), `name` = наименование изделия (графа 1), `material` = материал (графа 3); нужно хотя бы одно поле. Реализовано: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (индексированное свойство; `IText.Str` замещает содержимое без `Clear`) → `stamp.Update()`. Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`. Возвращает число заполненных граф.
**Vision — визуальная обратная связь (fallback)**
- `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**,
@@ -344,14 +345,14 @@ dotnet build -c Release -r win-x64 # сборка
## 10. Дорожная карта
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component+assembly_add_mate+drawing_create_standard_views):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели); **`create_variable`, `set_variable`, `delete_variable`** (управление переменными, package D start); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`); **`assembly_add_mate`** (сопряжения `coincidence`/`distance`, API7, `IMateConstraints3D`); **`drawing_create_standard_views`** (стандартные ассоциативные виды чертежа, API7, `DrawingService`).
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели); **`create_variable`, `set_variable`, `delete_variable`** (управление переменными, package D start); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`); **`assembly_add_mate`** (сопряжения `coincidence`/`distance`, API7, `IMateConstraints3D`); **`drawing_create_standard_views`** (стандартные ассоциативные виды чертежа, API7, `DrawingService`); **`drawing_fill_title_block`** (заполнение основной надписи/штампа: обозначение, наименование, материал; `IStamp.Text[id].Str`, API7, `DrawingService`).
**Следующие приоритеты:**
1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D). ⚠️ **Исследовано**: `ksCDimWithVariable` недоступен из внешней автоматизации — см. `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`.
2. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу.
3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
5. **2D-чертёж (продолжение):** оформление — размеры (`ILinearDimension`, …), рамка, штамп, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`).
5. **2D-чертёж (продолжение):** размеры на видах (`ILinearDimension`, …), рамка/формат, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`); основная надпись/штамп реализована (`drawing_fill_title_block`).
6. Сборки — продолжение: дополнительные типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`; `coincidence`/`distance` реализованы); авто-позиционирование компонентов; спецификации (`ISpecification`).
7. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
+4 -1
View File
@@ -190,4 +190,7 @@ stateful-сессии? Пока полагаемся на последовате
**Решено (инкремент 2).** Новый инструмент `assembly_add_mate(mateType, x1,y1,z1, x2,y2,z2, value=0)`. Паттерн API7: `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)``BaseObject1/2` (грани через `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue``Update()``RebuildModel(true)`; проверка `mate.Valid`. Типы `mc_Coincidence=0`/`mc_Distance=5` проверены вживую. Enum `MateType` + static `Mates` в `src/Kompas.Mcp.Core/Assemblies/MateType.cs`. Итог: **72 инструмента, 156 тестов (98 unit + 58 integration).** Следующий шаг: прочие типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`).
### ✅ Стандартные виды чертежа — реализованы (начало вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_create_standard_views(partFilePath, scale, x, y)` — создаёт ассоциативные виды (спереди/сверху/слева) на активном чертеже. Реализовано через API7 `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`), `DrawingTools`. Паттерн: `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, 20, 20)``object[]` как SAFEARRAY VT_I4. Проверено вживую: `Views.Count` 1→4, `ObjectCount>0` сразу (rebuild не нужен). Итог: **73 инструмента, 177 тестов (116 unit + 61 integration).** Следующий шаг: оформление чертежа (размеры, рамка, штамп).
**Решено.** Новый инструмент `drawing_create_standard_views(partFilePath, scale, x, y)` — создаёт ассоциативные виды (спереди/сверху/слева) на активном чертеже. Реализовано через API7 `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`), `DrawingTools`. Паттерн: `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, 20, 20)``object[]` как SAFEARRAY VT_I4. Проверено вживую: `Views.Count` 1→4, `ObjectCount>0` сразу (rebuild не нужен).
### ✅ Основная надпись (штамп) чертежа — реализована (инкремент 2 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_fill_title_block(designation?, name?, material?)` — заполняет графы основной надписи активного чертежа. Паттерн API7: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text``stamp.Update()`. `IText.Str` замещает содержимое напрямую (повторная запись перезаписывает — `Clear` не нужен, подтверждено round-trip тестом). Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`; инструмент в `DrawingTools.cs`. Итог: **74 инструмента, 188 тестов (123 unit + 65 integration).** Следующий шаг: размеры на видах.
+8 -8
View File
@@ -128,7 +128,7 @@
<!-- HERO -->
<header class="hero">
<div class="wrap">
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate+drawing_create_standard_views (73 инструмента)</span>
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block (74 инструмента)</span>
<h1>КОМПАС-3D <span class="g">MCP-сервер</span><br>управление CAD языком LLM</h1>
<p class="lead">MCP-сервер, который превращает операции КОМПАС-3D — создание документов,
эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом —
@@ -261,7 +261,7 @@
впереди.</p>
<div class="prog">
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · hole_counterbore · hole_countersink · hole_conic · draft (API5) · assembly_add_component · assembly_add_mate · drawing_create_standard_views · навык kompas-3d · 73 инструмента · 177 тестов</span></div>
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · hole_counterbore · hole_countersink · hole_conic · draft (API5) · assembly_add_component · assembly_add_mate · drawing_create_standard_views · drawing_fill_title_block · навык kompas-3d · 74 инструмента · 188 тестов</span></div>
<div class="bar"><i style="width:97%"></i></div>
</div>
@@ -297,7 +297,7 @@
<!-- TOOLS -->
<div class="panel">
<h3>Инструменты v3+ <span class="tag done">ГОТОВО</span></h3>
<p class="meta">73 инструмента, отдаются по MCP-протоколу</p>
<p class="meta">74 инструмента, отдаются по MCP-протоколу</p>
<ul class="list">
<li class="muted"><span class="mark ok"></span><span class="t"><code>System</code>: connect · status · set_visible</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Documents</code>: create · open · save · save_as · close · active</span></li>
@@ -310,8 +310,8 @@
<li class="muted"><span class="mark ok"></span><span class="t"><code>Variables</code>: <b>create_variable</b> · <b>set_variable</b> · <b>delete_variable</b> (пакет D)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Conversion</code>: import_step · export_step</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Assembly</code>: <b>assembly_add_component</b> (вставка компонента .m3d/.a3d в сборку, API7) · <b>assembly_add_mate</b> (сопряжения coincidence/distance, API7)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Drawing</code>: <b>drawing_create_standard_views</b> (стандартные ассоциативные виды чертежа, API7)</span></li>
<li><span class="mark wip"></span><span>2D-чертёж: оформление (размеры, рамка, штамп) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Drawing</code>: <b>drawing_create_standard_views</b> (стандартные ассоциативные виды чертежа, API7) · <b>drawing_fill_title_block</b> (основная надпись/штамп: обозначение, наименование, материал, API7)</span></li>
<li><span class="mark wip"></span><span>2D-чертёж: размеры/обозначения на видах, рамка/формат · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)</span></li>
</ul>
</div>
@@ -322,7 +322,7 @@
<ul class="list">
<li class="muted"><span class="mark ok"></span><span class="t">Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Итого 177 тестов зелёных (116 unit + 61 integration)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Итого 188 тестов зелёных (123 unit + 65 integration)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Сборка <code>kompas-mcp.exe</code>, README с конфигом клиента</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Навык <code>kompas-3d</code> (методика) + полигон <code>usecases/</code></span></li>
<li><span class="mark todo"></span><span>In-process тест MCP-клиента, релизный publish</span></li>
@@ -351,8 +351,8 @@
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">отверстия под крепёж · готово ✓</div><h4>Цековка, зенковка и конус</h4><p><b>hole_counterbore</b> (ksHTCounterbore) · <b>hole_countersink</b> (ksHTCountersinking) · <b>hole_conic</b> (ksHTConic, IConicHoleParameters) · HoleCore рефакторен (configure-колбэк) · 70 инструментов · 119 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">сборки start · готово ✓</div><h4>Вставка компонентов</h4><p><b>assembly_add_component</b> (API7 IParts7.AddFromFile + IPlacement3D)</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">сборки инкремент 2 · готово ✓</div><h4>Сопряжения</h4><p><b>assembly_add_mate</b> (coincidence/distance, API7 IMateConstraints3D) · IntegrationTestBase · 72 инструмента · 156 тестов</p></div>
<div class="tl cur"><div class="ph">2D-чертёж · начато ▶</div><h4>Стандартные виды</h4><p><b>drawing_create_standard_views</b> (AddStandartViews, API7) · 73 инструмента · 177 тестов · далее: оформление (размеры, рамка, штамп)</p></div>
<div class="tl"><div class="ph">далее · в очереди</div><h4>пакет E + прочее</h4><p>вспомогательная геометрия (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 2 ✓</div><h4>Стандартные виды + основная надпись</h4><p><b>drawing_create_standard_views</b> (AddStandartViews, API7) · <b>drawing_fill_title_block</b> (IStamp.Text[id].Str, API7) · 74 инструмента · 188 тестов · далее: размеры на видах</p></div>
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Размеры и оформление</h4><p>Размеры/обозначения на видах · рамка/формат · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
</div>
</div>
</section>
@@ -0,0 +1,129 @@
# Дизайн: основная надпись чертежа (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),
рамка/формат листа.
+65 -5
View File
@@ -30,6 +30,60 @@ public sealed class DrawingService
double x, double y, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => CreateStandardViewsCore(partFilePath, scale, x, y), ct);
/// <summary>
/// Заполнить графы основной надписи активного чертежа: обозначение (графа 2), наименование
/// изделия (графа 1), материал (графа 3). Передаются только нужные поля (null/пусто —
/// пропускается). Возвращает число заполненных граф.
/// </summary>
public Task<int> FillTitleBlockAsync(string? designation = null, string? name = null,
string? material = null, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => FillTitleBlockCore(designation, name, material), ct);
private int FillTitleBlockCore(string? designation, string? name, string? material)
{
var fields = StampFields.Collect(designation, name, material);
if (fields.Count == 0)
throw new ArgumentException("Укажите хотя бы одно поле основной надписи (designation/name/material).");
var stamp = RequireActiveStamp();
// Нетранзакционно: все графы записываются, затем один Update() фиксирует. При Update()==FALSE
// (на практике не воспроизводится) откат старых значений не делаем — он спекулятивен (Str уже
// на in-memory объектах графы, повторный Update мог бы тоже упасть); сообщаем об ошибке.
foreach (var (columnId, text) in fields)
RequireStampCell(stamp, columnId).Str = text; // Str замещает содержимое (Clear не нужен)
if (!stamp.Update())
throw new InvalidOperationException("IStamp.Update вернул FALSE — основная надпись не обновлена.");
return fields.Count;
}
/// <summary>Прочитать текст графы основной надписи (для проверки/диагностики).</summary>
public Task<string> ReadStampCellAsync(int columnId, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
var stamp = RequireActiveStamp();
return RequireStampCell(stamp, columnId).Str ?? string.Empty;
}, ct);
/// <summary>Получить графу основной надписи по номеру с диагностикой (номер &gt; 0, ячейка != null).</summary>
private static IText RequireStampCell(IStamp stamp, int columnId)
{
if (columnId <= 0)
throw new ArgumentOutOfRangeException(nameof(columnId), "Номер графы основной надписи должен быть > 0.");
return stamp.Text[columnId]
?? throw new InvalidOperationException($"Графа {columnId} основной надписи недоступна (Text[{columnId}] == null).");
}
private IStamp RequireActiveStamp()
{
var doc2d = RequireActiveDrawing();
var sheets = doc2d.LayoutSheets
?? throw new InvalidOperationException("Не удалось получить листы оформления чертежа.");
var sheet = sheets.ItemByNumber[1] as ILayoutSheet
?? throw new InvalidOperationException("На чертеже нет листа оформления #1.");
return sheet.Stamp as IStamp
?? throw new InvalidOperationException("У листа оформления нет основной надписи (Stamp).");
}
// Стандартная тройка проекций: ksPtFront=1, ksPtUp=3, ksPtLeft=5 (ksRelativeProjectionTypeEnum).
private static readonly object[] StandardProjections = { 1, 3, 5 };
private const int ExpectedCreated = 3;
@@ -79,19 +133,25 @@ public sealed class DrawingService
}
}
private IViews RequireActiveDrawingViews()
/// <summary>
/// Получить активный чертёж как <see cref="IKompasDocument2D"/>. Раздельные ошибки: нет документа /
/// тип != ksDocumentDrawing / не приводится к IKompasDocument2D.
/// </summary>
private IKompasDocument2D RequireActiveDrawing()
{
var doc = _session.Application.ActiveDocument
?? throw new InvalidOperationException("Нет активного документа. Создайте чертёж: document_create drawing.");
if (doc.DocumentType != DocumentTypeEnum.ksDocumentDrawing)
throw new InvalidOperationException(
$"Активный документ не является чертежом (тип: {doc.DocumentType}). Создайте чертёж: document_create drawing.");
if (doc is not IKompasDocument2D doc2d)
throw new InvalidOperationException("Активный документ не приводится к IKompasDocument2D.");
return doc2d.ViewsAndLayersManager?.Views
?? throw new InvalidOperationException("Не удалось получить коллекцию видов чертежа.");
return doc as IKompasDocument2D
?? throw new InvalidOperationException("Активный документ не приводится к IKompasDocument2D.");
}
private IViews RequireActiveDrawingViews()
=> RequireActiveDrawing().ViewsAndLayersManager?.Views
?? throw new InvalidOperationException("Не удалось получить коллекцию видов чертежа.");
private static HashSet<int> CollectViewNumbers(IViews views)
{
var set = new HashSet<int>();
@@ -0,0 +1,47 @@
namespace Kompas.Mcp.Core.Drawings;
/// <summary>
/// Поле основной надписи (графа штампа ГОСТ). Маппится на номер графы <c>ksStampEnum</c>.
/// Пока поддержаны три ключевые графы; остальные (масштаб, разработал, даты…) — позже.
/// </summary>
public enum StampField
{
/// <summary>Обозначение документа (графа 2, ksStDescription).</summary>
Designation,
/// <summary>Наименование изделия (графа 1, ksStPartNumber).</summary>
Name,
/// <summary>Материал (графа 3, ksStMaterial).</summary>
Material,
}
/// <summary>Маппинг поля основной надписи → номер графы и сбор непустых полей.</summary>
public static class StampFields
{
/// <summary>Номер графы (<c>ksStampEnum</c>) для поля основной надписи.</summary>
public static int ColumnId(StampField field) => field switch
{
StampField.Designation => 2, // ksStDescription
StampField.Name => 1, // ksStPartNumber (наименование изделия)
StampField.Material => 3, // ksStMaterial
_ => throw new ArgumentOutOfRangeException(nameof(field), field, "Неизвестное поле основной надписи."),
};
/// <summary>
/// Собрать непустые (после Trim) поля в список (номер графы, текст). null/пусто/пробелы —
/// пропускаются. Чистая (без COM) — unit-тестируемо.
/// </summary>
public static IReadOnlyList<(int ColumnId, string Text)> Collect(string? designation, string? name, string? material)
{
var list = new List<(int, string)>(3);
Add(list, StampField.Designation, designation);
Add(list, StampField.Name, name);
Add(list, StampField.Material, material);
return list;
}
private static void Add(List<(int, string)> list, StampField field, string? value)
{
if (!string.IsNullOrWhiteSpace(value))
list.Add((ColumnId(field), value.Trim()));
}
}
+15
View File
@@ -26,4 +26,19 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
var r = await drawing.CreateStandardViewsAsync(partFilePath, scale, x, y);
return $"Создано {r.Created} стандартных вида на чертеже (всего видов: {r.Total}, масштаб {scale}).";
}
[McpServerTool(Name = "drawing_fill_title_block")]
[Description("Заполнить графы основной надписи (штампа) активного чертежа. Передавайте только нужные поля: " +
"designation — обозначение документа (графа 2); name — наименование изделия (графа 1); " +
"material — материал (графа 3). Незаданные/пустые поля пропускаются (нужно хотя бы одно). " +
"Существующий текст графы перезаписывается. Возвращает число заполненных граф.")]
public async Task<string> FillTitleBlock(
[Description("Обозначение документа (графа 2)")] string? designation = null,
[Description("Наименование изделия (графа 1)")] string? name = null,
[Description("Материал (графа 3)")] string? material = null)
{
await session.ConnectAsync();
var filled = await drawing.FillTitleBlockAsync(designation, name, material);
return $"Заполнено граф основной надписи: {filled}.";
}
}
@@ -0,0 +1,85 @@
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Drawings;
using Kompas.Mcp.Core.Modeling;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: заполнение основной надписи чертежа (API7 IStamp.Text).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class DrawingStampTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly DrawingService _drawing;
private readonly PartModeler _modeler;
public DrawingStampTests(KompasFixture fx) : base(fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_drawing = new DrawingService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
}
[Fact]
public async Task Fills_and_reads_back_title_block_cells()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
var filled = await _drawing.FillTitleBlockAsync(
designation: "ДЕТАЛЬ.001", name: "Кронштейн", material: "Сталь 45");
Assert.Equal(3, filled);
// Явный reread каждой графы (графа 2/1/3).
Assert.Equal("ДЕТАЛЬ.001", await _drawing.ReadStampCellAsync(2));
Assert.Equal("Кронштейн", await _drawing.ReadStampCellAsync(1));
Assert.Equal("Сталь 45", await _drawing.ReadStampCellAsync(3));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Overwrites_existing_cell_text()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await _drawing.FillTitleBlockAsync(designation: "СТАРОЕ.000");
await _drawing.FillTitleBlockAsync(designation: "НОВОЕ.111");
// Перезапись, не дописывание.
Assert.Equal("НОВОЕ.111", await _drawing.ReadStampCellAsync(2));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Empty_input_throws()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await Assert.ThrowsAsync<ArgumentException>(
() => _drawing.FillTitleBlockAsync(null, " ", null));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Rejects_non_drawing_document()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Деталь — не чертёж: FillTitleBlock должен бросить.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddRectangleAsync(s, 0, 0, 10, 10);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await Assert.ThrowsAsync<InvalidOperationException>(
() => _drawing.FillTitleBlockAsync(designation: "X"));
}
finally { await _docs.CloseAsync(save: false); }
}
}
@@ -0,0 +1,41 @@
using Kompas.Mcp.Core.Drawings;
namespace Kompas.Mcp.Tests;
[Trait("Category", "Unit")]
public sealed class StampFieldsTests
{
[Theory]
[InlineData(StampField.Designation, 2)]
[InlineData(StampField.Name, 1)]
[InlineData(StampField.Material, 3)]
public void ColumnId_maps_known_fields(StampField field, int expected)
=> Assert.Equal(expected, StampFields.ColumnId(field));
[Fact]
public void ColumnId_throws_on_unknown()
=> Assert.Throws<ArgumentOutOfRangeException>(() => StampFields.ColumnId((StampField)999));
[Fact]
public void Collect_skips_null_empty_and_whitespace()
=> Assert.Empty(StampFields.Collect(null, "", " "));
[Fact]
public void Collect_trims_and_maps_provided_fields()
{
var result = StampFields.Collect(" ДЕТАЛЬ.001 ", null, "Сталь 45");
Assert.Equal(2, result.Count);
Assert.Contains(result, t => t.ColumnId == 2 && t.Text == "ДЕТАЛЬ.001"); // designation
Assert.Contains(result, t => t.ColumnId == 3 && t.Text == "Сталь 45"); // material
Assert.DoesNotContain(result, t => t.ColumnId == 1); // name не задан
}
[Fact]
public void Collect_includes_name_as_column_1()
{
var result = StampFields.Collect(null, "Кронштейн", null);
Assert.Single(result);
Assert.Equal((1, "Кронштейн"), result[0]);
}
}