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:
@@ -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
|
||||
|
||||
@@ -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** — как опция для удалённых клиентов.
|
||||
|
||||
|
||||
@@ -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).** Следующий шаг: размеры на видах.
|
||||
|
||||
@@ -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),
|
||||
рамка/формат листа.
|
||||
@@ -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>Получить графу основной надписи по номеру с диагностикой (номер > 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()));
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user