Merge feat/drawing-linear-dimension: линейный размер на виде (инкремент 3)
drawing_add_linear_dimension — линейный размер (horizontal/vertical/parallel) на ассоциативном виде через API7 (ISymbols2DContainer.LineDimensions, авто-значение). drawing_create_standard_views теперь возвращает ViewNumbers (адресация видов). 75 инструментов, 203 теста (131 unit + 72 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
|
||||
}
|
||||
```
|
||||
|
||||
## Инструменты (74 инструмента)
|
||||
## Инструменты (75 инструментов)
|
||||
|
||||
| Группа | Инструменты |
|
||||
|---|---|
|
||||
@@ -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_fill_title_block` |
|
||||
| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension` |
|
||||
| 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/ 188 тестов: unit + integration (integration требуют КОМПАС)
|
||||
tests/Kompas.Mcp.Tests/ 203 теста: 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`+`drawing_fill_title_block` (веха «2D-чертёж», инкремент 2)). Актуализировано по коду.
|
||||
> Статус: **реализовано и работает** (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`+`drawing_add_linear_dimension` (веха «2D-чертёж», инкремент 3)). Актуализировано по коду.
|
||||
> Сопутствующий контекст по 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) + интеграционные (требуют КОМПАС). 188 тестов (123 unit + 65 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 203 теста (131 unit + 72 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. |
|
||||
|
||||
Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
|
||||
|
||||
@@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
---
|
||||
|
||||
## 5. Карта инструментов (74 инструмента)
|
||||
## 5. Карта инструментов (75 инструментов)
|
||||
|
||||
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`.
|
||||
|
||||
@@ -209,8 +209,9 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
- `assembly_add_mate` — наложить сопряжение между гранями двух компонентов активной сборки по мировым точкам. Параметры: `mateType` (`coincidence` — грани заподлицо | `distance` — зазор `value` мм), `x1,y1,z1`, `x2,y2,z2`, `value=0`. Решатель позиционирует компоненты; возвращает статус `valid`. Реализовано через API7: `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)` → `BaseObject1/2` (грани через `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` → `Update()` → `RebuildModel(true)`; проверка `mate.Valid` (false → ошибка + откат `mate.Owner.Delete`). Enum `MateType { Coincidence, Distance }` в `src/Kompas.Mcp.Core/Assemblies/MateType.cs`; маппинг на `MateConstraintType` (`mc_Coincidence=0`, `mc_Distance=5`). Scope: `coincidence`/`distance` проверены вживую; прочие типы (`parallel`, `concentric`, …) — будущие инкременты.
|
||||
|
||||
**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_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,ViewNumbers}` — `ViewNumbers` содержит номера созданных видов (для адресации при простановке размеров). Модель должна быть сохранена до вызова.
|
||||
- `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`. Возвращает число заполненных граф.
|
||||
- `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — поставить линейный размер на виде активного чертежа. Координаты (x1,y1)-(x2,y2) — выносные точки в **локальной СК вида** (мм); (x3,y3) — положение размерной линии; `orientation`: `horizontal`|`vertical`|`parallel`; `viewNumber` — номер вида (0=первый). Значение измеряется автоматически (`AutoNominalValue=true`). Реализовано: `(ISymbols2DContainer)view` (COM-QI от конкретного `IView`, не от листа — размер попадает именно в этот вид) → `LineDimensions.Add()` → `ILineDimension` → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Ключевая находка: **размеры не входят в `IView.ObjectCount`** (он считает геометрию `IDrawingContainer`); проверять через `ISymbols2DContainer.LineDimensions.Count`. Откат через `dim.Delete()` при ошибке или нулевом значении. Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`.
|
||||
|
||||
**Vision — визуальная обратная связь (fallback)**
|
||||
- `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**,
|
||||
@@ -345,14 +346,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+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`).
|
||||
**Реализовано (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+drawing_add_linear_dimension):** документы, эскизы (полный набор 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`); **`drawing_add_linear_dimension`** (линейный размер на виде чертежа: `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`, координаты в локальной СК вида, 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`); основная надпись/штамп реализована (`drawing_fill_title_block`).
|
||||
5. **2D-чертёж (продолжение):** диаметральные/радиальные/угловые размеры, рамка/формат, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`); основная надпись/штамп реализована (`drawing_fill_title_block`); линейные размеры реализованы (`drawing_add_linear_dimension`).
|
||||
6. Сборки — продолжение: дополнительные типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`; `coincidence`/`distance` реализованы); авто-позиционирование компонентов; спецификации (`ISpecification`).
|
||||
7. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
|
||||
|
||||
|
||||
@@ -194,3 +194,6 @@ stateful-сессии? Пока полагаемся на последовате
|
||||
|
||||
### ✅ Основная надпись (штамп) чертежа — реализована (инкремент 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).** Следующий шаг: размеры на видах.
|
||||
|
||||
### ✅ Линейные размеры на видах чертежа — реализованы (инкремент 3 вехи «2D-чертёж»)
|
||||
**Решено.** Новый инструмент `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — ставит линейный размер на виде активного чертежа. Паттерн API7: получить вид по `viewNumber` → COM-QI `(ISymbols2DContainer)view` (размер адресуется конкретному виду, а не листу) → `LineDimensions.Add()` → `ILineDimension` → `X1,Y1,X2,Y2` (выносные точки в **локальной СК вида**, мм), `X3,Y3` (положение размерной линии), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true` → `Update()` → `Valid` → значение `((IDimensionText)dim).NominalValue`. Откат `dim.Delete()` при ошибке или нулевом значении. **Ключевая находка: размеры НЕ входят в `IView.ObjectCount`** (тот считает геометрию `IDrawingContainer`); размеры живут в `ISymbols2DContainer` отдельно — попадание размера в вид проверяется через `ISymbols2DContainer.LineDimensions.Count`. `DrawingViewsResult` расширен полем `ViewNumbers` (номера созданных видов — для адресации при простановке размеров). Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. Итог: **75 инструментов, 203 теста (131 unit + 72 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+drawing_fill_title_block (74 инструмента)</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+drawing_add_linear_dimension (75 инструментов)</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 · drawing_fill_title_block · навык kompas-3d · 74 инструмента · 188 тестов</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 · drawing_add_linear_dimension · навык kompas-3d · 75 инструментов · 203 теста</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">74 инструмента, отдаются по MCP-протоколу</p>
|
||||
<p class="meta">75 инструментов, отдаются по 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) · <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>
|
||||
<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) · <b>drawing_add_linear_dimension</b> (линейный размер на виде, ISymbols2DContainer, 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">Итого 188 тестов зелёных (123 unit + 65 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 203 теста зелёных (131 unit + 72 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,9 @@
|
||||
<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" 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 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" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 3 ✓</div><h4>Линейные размеры</h4><p><b>drawing_add_linear_dimension</b> (ISymbols2DContainer/LineDimensions.Add/ILineDimension, локальная СК вида, API7) · ViewNumbers в результате видов · 75 инструментов · 203 теста · далее: диаметральные/радиальные размеры, рамка/формат</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,138 @@
|
||||
# Дизайн: линейный размер на виде чертежа (drawing_add_linear_dimension) через API7
|
||||
|
||||
**Дата:** 2026-05-27
|
||||
**Статус:** реализовано и проверено (7 интеграционных + unit, всего 203); ревью Codex спека и реализации учтено
|
||||
|
||||
## Правки по ревью реализации Codex
|
||||
|
||||
- **#4 нулевое значение** (важно): после `Update`+`Valid` проверяем `|NominalValue| > 1e-6` — ловит
|
||||
несовместимость ориентации с точками (horizontal для точек на одной вертикали: `|X2-X1|=0`), которую
|
||||
`RequireDistinctPoints` не видит. При нуле — откат (`dim.Delete`) + ошибка. Тест добавлен.
|
||||
- **#3 Parallel** (важно): добавлен интеграционный тест — параллельный (0,0)-(30,40) → 50 (3-4-5).
|
||||
**Работает БЕЗ явного `Angle`** (КОМПАС авто-вычисляет угол по точкам) — Parallel оставлен.
|
||||
- **#7 rollback** (важно): тест нулевой проекции проходит через ветку `dim.Delete()` (откат) —
|
||||
проверяет, что число размеров вида не изменилось. (Update==false/Valid==false без fault injection.)
|
||||
- **#8 ассоциативность вида** (минор): осознанный контракт «первый НЕсистемный вид» (задокументирован
|
||||
в описании инструмента и ошибке FindView).
|
||||
|
||||
## Находка реализации: размеры НЕ в IView.ObjectCount
|
||||
|
||||
`IView.ObjectCount` считает **геометрию** (`IDrawingContainer`: линии/дуги), а размеры живут в
|
||||
`ISymbols2DContainer` отдельно → после простановки размера `ObjectCount` НЕ меняется (проверено: 4→4).
|
||||
Поэтому «размер попал в целевой вид» проверяем счётом `ISymbols2DContainer.LineDimensions.Count`
|
||||
(helper `GetViewLineDimensionCountAsync`), а не `ObjectCount`. (Гарантия непустоты у стандартных видов
|
||||
по `ObjectCount` остаётся верной — там именно геометрия.)
|
||||
|
||||
## Правки по ревью Codex
|
||||
|
||||
- **Нулевая длина** (#1): `DrawingValidation.RequireDistinctPoints(x1,y1,x2,y2)` — расстояние между
|
||||
точками > 1e-7 (иначе вырожденный размер); + проверка `dim.Valid` после `Update()` (откат);
|
||||
+ негативный тест.
|
||||
- **Контракт вида** (#4): честно — «первый НЕСИСТЕМНЫЙ вид (Number != 0)» при `viewNumber<=0`, без
|
||||
претензии на ассоциативность и без QI к `IAssociationView` (в наших чертежах иных видов нет).
|
||||
- **null-check + порядок** (#9): `LineDimensions.Add()` проверять на null; `AutoNominalValue=true`
|
||||
выставлять явно; `NominalValue` читать ТОЛЬКО после `Update()==true && Valid==true`.
|
||||
- **ViewNumbers** (#7): поле `required` в `DrawingViewsResult`; инициализатор и текст MCP-ответа
|
||||
`drawing_create_standard_views` обновить (вернуть номера видов).
|
||||
- **Тесты** (#6): ObjectCount ЦЕЛЕВОГО вида до/после; адресация конкретного `viewNumber`; негатив на
|
||||
совпадающие точки. **viewNumber не найден** (#2) → понятная ошибка. 3D-конвертер точек (#8) — будущее.
|
||||
|
||||
## Цель
|
||||
|
||||
Веха 2D-ЧЕРТЁЖ, инкремент 3 (размеры). После видов и основной надписи — **линейный размер** на
|
||||
ассоциативном виде: между двумя точками (в локальной СК вида) с авто-измерением длины. Размеры —
|
||||
самый ценный элемент оформления (несут размерную информацию). Начинаем с линейного
|
||||
(горизонтальный/вертикальный/параллельный); диаметральные/радиальные/угловые — далее.
|
||||
|
||||
## Спайк: механизм подтверждён вживую (НЕ разучивать)
|
||||
|
||||
Спайк (`DrawingDimensionSpikeTests`, зелёный) подтвердил путь API7:
|
||||
|
||||
```
|
||||
IView view = первый вид с Number != 0; // системный вид — #0, пропускаем
|
||||
ISymbols2DContainer symbols = (ISymbols2DContainer)view; // COM-QI ОТ ВИДА (не от документа!)
|
||||
ILineDimension dim = symbols.LineDimensions.Add();
|
||||
dim.X1=x1; dim.Y1=y1; dim.X2=x2; dim.Y2=y2; // выносные точки (ЛОКАЛЬНАЯ СК вида, мм)
|
||||
dim.X3=x3; dim.Y3=y3; // положение размерной линии
|
||||
dim.Orientation = ksLinDHorizontal; // 0=Parallel / 1=Horizontal / 2=Vertical
|
||||
bool ok = dim.Update();
|
||||
double value = ((IDimensionText)dim).NominalValue; // авто-измеренное значение (мм)
|
||||
```
|
||||
|
||||
**Проверено (вид спереди коробки 40×30×20):** размер (0,0)-(40,0) горизонтальный →
|
||||
`Update()=True`, `Valid=True`, `NominalValue == 40` (авто `|X2-X1|`), `view.ObjectCount` вырос.
|
||||
|
||||
**Подтверждённые факты:**
|
||||
- **`ISymbols2DContainer` берётся COM-QI ОТ КОНКРЕТНОГО `IView`** — размер попадает именно в этот
|
||||
вид (не нужен `Current=true`). (Аналогично 3D: `part`→`IModelContainer`.)
|
||||
- **Координаты X1..Y3 — ЛОКАЛЬНАЯ СК вида** (= координаты модели на плоскости вида, при масштабе
|
||||
1:1 совпадают с мм модели). НЕ координаты листа.
|
||||
- **`AutoNominalValue=true` по умолчанию** → `NominalValue` = расстояние между точками
|
||||
(горизонтальный: `|X2-X1|`, вертикальный: `|Y2-Y1|`, параллельный: реальная длина). Значение
|
||||
читается `((IDimensionText)dim).NominalValue` (QI).
|
||||
- `Orientation` (`ksLineDimensionOrientationEnum`): `ksLinDParallel=0`, `ksLinDHorizontal=1`,
|
||||
`ksLinDVertical=2` (в `Kompas6Constants`).
|
||||
- `Update()` обязателен; перестроение документа не нужно.
|
||||
|
||||
## Адресация вида (часть инкремента)
|
||||
|
||||
Чтобы указать, НА КАКОМ виде ставить размер, нужен его `Number`. Сейчас
|
||||
`drawing_create_standard_views` возвращает только `{Created, Total}`. **Расширяем `DrawingViewsResult`
|
||||
полем `ViewNumbers` (номера созданных видов)** — уже собираются как `newViews` в
|
||||
`CreateStandardViewsCore` (тривиально: `newViews.Select(v => v.Number)`). Это закрывает замечание
|
||||
Codex из ревью видов (#10: «нет ссылок на созданные виды») и делает размеры адресуемыми.
|
||||
|
||||
## MCP-инструмент
|
||||
|
||||
| Инструмент | Параметры | Поведение |
|
||||
|---|---|---|
|
||||
| `drawing_add_linear_dimension` | `x1,y1,x2,y2` (выносные точки), `x3,y3` (положение размерной линии), `orientation="horizontal"`, `viewNumber=0` | Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ СК вида (мм). `orientation`: horizontal/vertical/parallel. `viewNumber`: номер вида (0 = первый ассоциативный/главный). Значение измеряется автоматически. Возвращает измеренное значение и номер вида. |
|
||||
|
||||
Контракт: активный документ — чертёж; на чертеже есть хотя бы один ассоциативный вид. Координаты —
|
||||
локальные вида (см. выше). `drawing_create_standard_views` возвращает `ViewNumbers` для адресации.
|
||||
|
||||
## Архитектура
|
||||
|
||||
В существующем `DrawingService`. Свой enum `DimensionOrientation` (паттерн `MateType`/`StampField`).
|
||||
|
||||
- `src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs` — enum + static `DimensionOrientations`
|
||||
(`Parse(string)`, `ToKompas` → `ksLineDimensionOrientationEnum`).
|
||||
- `DrawingService.AddLinearDimensionAsync(viewNumber, x1,y1,x2,y2,x3,y3, DimensionOrientation, ct)`.
|
||||
- `DrawingViewsResult` += `IReadOnlyList<int> ViewNumbers`.
|
||||
- Инструмент `drawing_add_linear_dimension` в `DrawingTools` (orientation строкой → Parse).
|
||||
|
||||
## Валидация (чистые static, unit-тест)
|
||||
|
||||
- `DimensionOrientations.Parse` — horizontal/vertical/parallel (+рус.) → enum; иначе `ArgumentException`.
|
||||
- `DimensionOrientations.ToKompas` — enum → `ksLineDimensionOrientationEnum` (0/1/2).
|
||||
- `DrawingValidation.RequireFiniteCoords(params)` — координаты конечны (NaN/Infinity → ошибка).
|
||||
|
||||
## Реализация
|
||||
|
||||
`AddLinearDimensionCore`: валидация координат + orientation → `RequireActiveDrawing` → найти вид
|
||||
(`viewNumber<=0` → первый с `Number!=0`; иначе по `Number`, ошибка если нет) → `(ISymbols2DContainer)view`
|
||||
→ `LineDimensions.Add()` → задать X1..Y3 + Orientation → `Update()` (FALSE → откат `dim.Delete()` +
|
||||
ошибка) → проверить `dim.Valid` (false → откат + ошибка) → вернуть `AddedDimension { Value =
|
||||
((IDimensionText)dim).NominalValue, ViewNumber = view.Number }`. RCW точечно не освобождаем (v2-2).
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Unit (`DimensionOrientationsTests`)
|
||||
- `Parse` — horizontal/vertical/parallel и рус.-синонимы → enum; бросает на мусоре.
|
||||
- `ToKompas` — каждый → 1/2/0.
|
||||
- `RequireFiniteCoords` — бросает на NaN/Infinity; пропускает конечные.
|
||||
|
||||
### Integration (`DrawingDimensionTests`)
|
||||
Паттерн: построить деталь → сохранить → создать чертёж → стандартные виды → размер → проверить →
|
||||
`finally CloseAsync` (база `IntegrationTestBase`).
|
||||
1. **Горизонтальный** (0,0)-(40,0) → `Value ≈ 40`; `view.ObjectCount` вырос.
|
||||
2. **Вертикальный** (0,0)-(0,20) → `Value ≈ 20`.
|
||||
3. **Адресация вида**: `CreateStandardViewsAsync` вернул `ViewNumbers` (3 шт); размер с конкретным
|
||||
`viewNumber` ставится в этот вид.
|
||||
4. **Нет видов**: чертёж без видов → понятная ошибка.
|
||||
|
||||
(Сценарий 1 = промотированный спайк; спайк-файл удаляется.)
|
||||
|
||||
## Дальнейшее (вне спека)
|
||||
- Диаметральные/радиальные/угловые размеры; привязка к геометрии вида (ассоциативно, `BaseObject`);
|
||||
допуски/обозначения; рамка/формат листа.
|
||||
@@ -0,0 +1,41 @@
|
||||
using Kompas6Constants;
|
||||
|
||||
namespace Kompas.Mcp.Core.Drawings;
|
||||
|
||||
/// <summary>Ориентация линейного размера (обёртка над <see cref="ksLineDimensionOrientationEnum"/>).</summary>
|
||||
public enum DimensionOrientation
|
||||
{
|
||||
/// <summary>Горизонтальный (измеряет |X2−X1|).</summary>
|
||||
Horizontal,
|
||||
/// <summary>Вертикальный (измеряет |Y2−Y1|).</summary>
|
||||
Vertical,
|
||||
/// <summary>Параллельный отрезку P1→P2 (реальная длина).</summary>
|
||||
Parallel,
|
||||
}
|
||||
|
||||
/// <summary>Маппинг ориентации размера ↔ перечисление КОМПАС и разбор строки.</summary>
|
||||
public static class DimensionOrientations
|
||||
{
|
||||
public static ksLineDimensionOrientationEnum ToKompas(DimensionOrientation orientation) => orientation switch
|
||||
{
|
||||
DimensionOrientation.Horizontal => ksLineDimensionOrientationEnum.ksLinDHorizontal,
|
||||
DimensionOrientation.Vertical => ksLineDimensionOrientationEnum.ksLinDVertical,
|
||||
DimensionOrientation.Parallel => ksLineDimensionOrientationEnum.ksLinDParallel,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(orientation), orientation, "Неизвестная ориентация размера."),
|
||||
};
|
||||
|
||||
/// <summary>Разобрать строку ориентации (англ./рус.) в <see cref="DimensionOrientation"/>.</summary>
|
||||
public static DimensionOrientation Parse(string value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"horizontal" or "h" or "горизонтальный" or "горизонтальная" => DimensionOrientation.Horizontal,
|
||||
"vertical" or "v" or "вертикальный" or "вертикальная" => DimensionOrientation.Vertical,
|
||||
"parallel" or "p" or "параллельный" or "параллельная" => DimensionOrientation.Parallel,
|
||||
_ => throw new ArgumentException(
|
||||
$"Неизвестная ориентация размера '{value}'. Допустимо: horizontal, vertical, parallel.",
|
||||
nameof(value)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Kompas.Mcp.Core.Drawings;
|
||||
|
||||
/// <summary>Результат простановки размера на чертеже.</summary>
|
||||
public sealed record DrawingDimensionResult
|
||||
{
|
||||
/// <summary>Измеренное значение размера (мм).</summary>
|
||||
public required double Value { get; init; }
|
||||
|
||||
/// <summary>Номер вида, на котором поставлен размер.</summary>
|
||||
public required int ViewNumber { get; init; }
|
||||
}
|
||||
@@ -84,6 +84,85 @@ public sealed class DrawingService
|
||||
?? throw new InvalidOperationException("У листа оформления нет основной надписи (Stamp).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ
|
||||
/// СК вида (мм); размерная линия проходит через (x3,y3). <paramref name="viewNumber"/> — номер вида
|
||||
/// (≤0 = первый несистемный/главный). Значение измеряется автоматически. Возвращает значение и номер вида.
|
||||
/// </summary>
|
||||
public Task<DrawingDimensionResult> AddLinearDimensionAsync(int viewNumber, double x1, double y1,
|
||||
double x2, double y2, double x3, double y3, DimensionOrientation orientation, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
AddLinearDimensionCore(viewNumber, x1, y1, x2, y2, x3, y3, orientation), ct);
|
||||
|
||||
private DrawingDimensionResult AddLinearDimensionCore(int viewNumber, double x1, double y1,
|
||||
double x2, double y2, double x3, double y3, DimensionOrientation orientation)
|
||||
{
|
||||
DrawingValidation.RequireFiniteCoords(x1, y1, x2, y2, x3, y3);
|
||||
DrawingValidation.RequireDistinctPoints(x1, y1, x2, y2);
|
||||
|
||||
var view = FindView(viewNumber);
|
||||
var symbols = view as ISymbols2DContainer
|
||||
?? throw new InvalidOperationException("Вид не приводится к ISymbols2DContainer.");
|
||||
var dim = symbols.LineDimensions?.Add()
|
||||
?? throw new InvalidOperationException("LineDimensions.Add вернул null.");
|
||||
try
|
||||
{
|
||||
dim.X1 = x1; dim.Y1 = y1; dim.X2 = x2; dim.Y2 = y2; dim.X3 = x3; dim.Y3 = y3;
|
||||
dim.Orientation = DimensionOrientations.ToKompas(orientation);
|
||||
((IDimensionText)dim).AutoNominalValue = true; // авто-измерение значения (по умолчанию, явно)
|
||||
|
||||
if (!dim.Update())
|
||||
throw new InvalidOperationException("ILineDimension.Update вернул FALSE — размер не построен.");
|
||||
if (!dim.Valid)
|
||||
throw new InvalidOperationException("Размер построен невалидным (проверьте точки/ориентацию).");
|
||||
|
||||
// NominalValue читаем ТОЛЬКО после успешного Update и Valid.
|
||||
var value = ((IDimensionText)dim).NominalValue;
|
||||
// Нулевое значение — ориентация несовместима с точками (напр. horizontal для точек на одной
|
||||
// вертикали: |X2-X1|=0). RequireDistinctPoints это не ловит — точки различны по Y.
|
||||
if (Math.Abs(value) < 1e-6)
|
||||
throw new InvalidOperationException(
|
||||
"Размер измерил нулевое значение — выбранная ориентация несовместима с точками " +
|
||||
"(например, horizontal для точек на одной вертикали; используйте vertical/parallel).");
|
||||
return new DrawingDimensionResult { Value = value, ViewNumber = view.Number };
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { dim.Delete(); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Число линейных размеров на виде по номеру (для проверки/диагностики). Размеры живут
|
||||
/// в ISymbols2DContainer вида, а НЕ в IView.ObjectCount (тот считает геометрию).</summary>
|
||||
public Task<int> GetViewLineDimensionCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var symbols = (ISymbols2DContainer)FindView(viewNumber);
|
||||
return symbols.LineDimensions?.Count ?? 0;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Найти вид: при viewNumber ≤ 0 — первый несистемный (Number != 0); иначе по номеру.</summary>
|
||||
private IView FindView(int viewNumber)
|
||||
{
|
||||
var views = RequireActiveDrawingViews();
|
||||
foreach (var o in views)
|
||||
{
|
||||
if (o is not IView v) continue;
|
||||
if (viewNumber <= 0)
|
||||
{
|
||||
if (v.Number != 0) return v; // первый несистемный
|
||||
}
|
||||
else if (v.Number == viewNumber)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException(viewNumber <= 0
|
||||
? "На чертеже нет несистемного вида. Сначала создайте виды: drawing_create_standard_views."
|
||||
: $"Вид с номером {viewNumber} не найден на чертеже.");
|
||||
}
|
||||
|
||||
// Стандартная тройка проекций: ksPtFront=1, ksPtUp=3, ksPtLeft=5 (ksRelativeProjectionTypeEnum).
|
||||
private static readonly object[] StandardProjections = { 1, 3, 5 };
|
||||
private const int ExpectedCreated = 3;
|
||||
@@ -122,7 +201,12 @@ public sealed class DrawingService
|
||||
throw new InvalidOperationException(
|
||||
"Стандартные виды созданы, но пусты (модель не спроецировалась). Проверьте файл модели.");
|
||||
|
||||
return new DrawingViewsResult { Created = newViews.Count, Total = views.Count };
|
||||
return new DrawingViewsResult
|
||||
{
|
||||
Created = newViews.Count,
|
||||
Total = views.Count,
|
||||
ViewNumbers = newViews.Select(v => v.Number).ToArray(),
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -32,4 +32,26 @@ public static class DrawingValidation
|
||||
if (!double.IsFinite(scale) || scale <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(scale), "Масштаб должен быть конечным и > 0.");
|
||||
}
|
||||
|
||||
/// <summary>Проверить, что все координаты конечны (отсекает NaN/Infinity).</summary>
|
||||
public static void RequireFiniteCoords(params double[] coords)
|
||||
{
|
||||
foreach (var c in coords)
|
||||
if (!double.IsFinite(c))
|
||||
throw new ArgumentException("Координаты должны быть конечными числами (не NaN/Infinity).");
|
||||
}
|
||||
|
||||
/// <summary>Минимальное расстояние между выносными точками линейного размера (мм).</summary>
|
||||
private const double MinPointDistance = 1e-7;
|
||||
|
||||
/// <summary>
|
||||
/// Проверить, что выносные точки размера различны (иначе размер вырожден — нулевая длина).
|
||||
/// </summary>
|
||||
public static void RequireDistinctPoints(double x1, double y1, double x2, double y2)
|
||||
{
|
||||
var dx = x2 - x1;
|
||||
var dy = y2 - y1;
|
||||
if (Math.Sqrt(dx * dx + dy * dy) < MinPointDistance)
|
||||
throw new ArgumentException("Выносные точки размера совпадают — нельзя поставить размер нулевой длины.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,7 @@ public sealed record DrawingViewsResult
|
||||
|
||||
/// <summary>Общее число видов на чертеже после операции (включая системный вид).</summary>
|
||||
public required int Total { get; init; }
|
||||
|
||||
/// <summary>Номера созданных видов (для адресации при простановке размеров).</summary>
|
||||
public required IReadOnlyList<int> ViewNumbers { get; init; }
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var r = await drawing.CreateStandardViewsAsync(partFilePath, scale, x, y);
|
||||
return $"Создано {r.Created} стандартных вида на чертеже (всего видов: {r.Total}, масштаб {scale}).";
|
||||
var numbers = string.Join(", ", r.ViewNumbers);
|
||||
return $"Создано {r.Created} стандартных вида на чертеже (всего видов: {r.Total}, масштаб {scale}). " +
|
||||
$"Номера созданных видов: {numbers} (для drawing_add_linear_dimension).";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_fill_title_block")]
|
||||
@@ -41,4 +43,26 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
|
||||
var filled = await drawing.FillTitleBlockAsync(designation, name, material);
|
||||
return $"Заполнено граф основной надписи: {filled}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_add_linear_dimension")]
|
||||
[Description("Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) " +
|
||||
"в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты модели на плоскости вида). Размерная " +
|
||||
"линия проходит через (x3,y3). orientation: horizontal (|X2-X1|) | vertical (|Y2-Y1|) | parallel " +
|
||||
"(длина отрезка). viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный вид). " +
|
||||
"Значение измеряется автоматически. Возвращает измеренное значение и номер вида.")]
|
||||
public async Task<string> AddLinearDimension(
|
||||
[Description("X первой выносной точки (СК вида, мм)")] double x1,
|
||||
[Description("Y первой выносной точки")] double y1,
|
||||
[Description("X второй выносной точки")] double x2,
|
||||
[Description("Y второй выносной точки")] double y2,
|
||||
[Description("X положения размерной линии")] double x3,
|
||||
[Description("Y положения размерной линии")] double y3,
|
||||
[Description("Ориентация: horizontal | vertical | parallel")] string orientation = "horizontal",
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var o = DimensionOrientations.Parse(orientation);
|
||||
var r = await drawing.AddLinearDimensionAsync(viewNumber, x1, y1, x2, y2, x3, y3, o);
|
||||
return $"Линейный размер ({orientation}) поставлен на виде {r.ViewNumber}, значение {r.Value:0.###} мм.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Kompas.Mcp.Core.Drawings;
|
||||
|
||||
namespace Kompas.Mcp.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DimensionOrientationsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("horizontal", DimensionOrientation.Horizontal)]
|
||||
[InlineData("vertical", DimensionOrientation.Vertical)]
|
||||
[InlineData("parallel", DimensionOrientation.Parallel)]
|
||||
[InlineData("ГОРИЗОНТАЛЬНЫЙ", DimensionOrientation.Horizontal)]
|
||||
[InlineData(" vertical ", DimensionOrientation.Vertical)]
|
||||
public void Parse_maps_known(string value, DimensionOrientation expected)
|
||||
=> Assert.Equal(expected, DimensionOrientations.Parse(value));
|
||||
|
||||
[Fact]
|
||||
public void Parse_throws_on_unknown()
|
||||
=> Assert.Throws<ArgumentException>(() => DimensionOrientations.Parse("diagonal"));
|
||||
|
||||
// ToKompas (→ ksLineDimensionOrientationEnum) намеренно НЕ покрыт unit-тестом: тестовый проект
|
||||
// не ссылается на Kompas6Constants (граница проекта). Маппинг тривиален и проверяется интеграцией
|
||||
// (horizontal → |X2-X1|, vertical → |Y2-Y1|).
|
||||
}
|
||||
@@ -53,4 +53,19 @@ public sealed class DrawingValidationTests
|
||||
[InlineData(2.0)]
|
||||
public void RequireScale_accepts_positive(double scale)
|
||||
=> Assert.Null(Record.Exception(() => DrawingValidation.RequireScale(scale)));
|
||||
|
||||
[Fact]
|
||||
public void RequireFiniteCoords_throws_on_non_finite()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => DrawingValidation.RequireFiniteCoords(0, double.NaN));
|
||||
Assert.Throws<ArgumentException>(() => DrawingValidation.RequireFiniteCoords(double.PositiveInfinity));
|
||||
Assert.Null(Record.Exception(() => DrawingValidation.RequireFiniteCoords(0, 1, -5, 40)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequireDistinctPoints_throws_on_coincident()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => DrawingValidation.RequireDistinctPoints(10, 20, 10, 20));
|
||||
Assert.Null(Record.Exception(() => DrawingValidation.RequireDistinctPoints(0, 0, 40, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Drawings;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: линейный размер на виде чертежа (API7 ISymbols2DContainer.LineDimensions).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class DrawingDimensionTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly DrawingService _drawing;
|
||||
|
||||
public DrawingDimensionTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
_drawing = new DrawingService(fx.Session, fx.Dispatcher);
|
||||
}
|
||||
|
||||
private async Task<string> BuildAndSaveBoxAsync()
|
||||
{
|
||||
var path = TestPaths.NewFile(".m3d");
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddRectangleAsync(s, 0, 0, 40, 30);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 20);
|
||||
await _modeler.RebuildAsync();
|
||||
await _docs.SaveAsAsync(path);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
return path;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Horizontal_dimension_auto_measures_distance()
|
||||
{
|
||||
var partFile = await BuildAndSaveBoxAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
Assert.Equal(3, views.ViewNumbers.Count);
|
||||
|
||||
var r = await _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: 0, x1: 0, y1: 0, x2: 40, y2: 0, x3: 20, y3: -10, DimensionOrientation.Horizontal);
|
||||
|
||||
Assert.InRange(r.Value, 39.9, 40.1); // авто-значение = |X2-X1|
|
||||
Assert.Contains(r.ViewNumber, views.ViewNumbers); // размер попал в один из видов
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Vertical_dimension_measures_y_span()
|
||||
{
|
||||
var partFile = await BuildAndSaveBoxAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var r = await _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: 0, x1: 0, y1: 0, x2: 0, y2: 20, x3: -10, y3: 10, DimensionOrientation.Vertical);
|
||||
Assert.InRange(r.Value, 19.9, 20.1);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Targets_specific_view_by_number_and_increments_its_object_count()
|
||||
{
|
||||
var partFile = await BuildAndSaveBoxAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var targetNumber = views.ViewNumbers[1]; // второй созданный вид (напр. сверху)
|
||||
|
||||
// Размеры живут в ISymbols2DContainer вида — считаем именно их у ЦЕЛЕВОГО вида.
|
||||
var before = await _drawing.GetViewLineDimensionCountAsync(targetNumber);
|
||||
var r = await _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: targetNumber, x1: 0, y1: 0, x2: 40, y2: 0, x3: 20, y3: -10, DimensionOrientation.Horizontal);
|
||||
var after = await _drawing.GetViewLineDimensionCountAsync(targetNumber);
|
||||
|
||||
Assert.Equal(targetNumber, r.ViewNumber);
|
||||
Assert.Equal(before + 1, after); // размер попал именно в целевой вид
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Parallel_dimension_measures_true_length()
|
||||
{
|
||||
var partFile = await BuildAndSaveBoxAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
// Параллельный размер (0,0)-(30,40): реальная длина = 50 (3-4-5).
|
||||
var r = await _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: 0, x1: 0, y1: 0, x2: 30, y2: 40, x3: 25, y3: 5, DimensionOrientation.Parallel);
|
||||
Assert.InRange(r.Value, 49.5, 50.5);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Zero_projection_orientation_throws_and_rolls_back()
|
||||
{
|
||||
var partFile = await BuildAndSaveBoxAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var before = await _drawing.GetViewLineDimensionCountAsync(0);
|
||||
|
||||
// Горизонтальный размер для точек на одной вертикали → |X2-X1|=0 → ошибка + откат.
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: 0, x1: 10, y1: 0, x2: 10, y2: 30, x3: 0, y3: 15, DimensionOrientation.Horizontal));
|
||||
|
||||
// Откат: число размеров не изменилось (вырожденный размер удалён).
|
||||
var after = await _drawing.GetViewLineDimensionCountAsync(0);
|
||||
Assert.Equal(before, after);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Coincident_points_throw()
|
||||
{
|
||||
var partFile = await BuildAndSaveBoxAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: 0, x1: 10, y1: 10, x2: 10, y2: 10, x3: 0, y3: 0, DimensionOrientation.Horizontal));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_views_throws()
|
||||
{
|
||||
// Чертёж без ассоциативных видов: только системный вид → понятная ошибка.
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _drawing.AddLinearDimensionAsync(
|
||||
viewNumber: 0, x1: 0, y1: 0, x2: 40, y2: 0, x3: 20, y3: -10, DimensionOrientation.Horizontal));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user