Merge feat/assembly-add-mate: сопряжения компонентов сборки + IntegrationTestBase
assembly_add_mate (инкремент 2 вехи СБОРКИ): сопряжения coincidence/distance между гранями компонентов (API7 IMateConstraints3D). + рефакторинг тестов на IntegrationTestBase (per-test очистка документов). 72 инструмента, 156 тестов (98 unit + 58 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
|
||||
}
|
||||
```
|
||||
|
||||
## Инструменты (71 инструмент)
|
||||
## Инструменты (72 инструмента)
|
||||
|
||||
| Группа | Инструменты |
|
||||
|---|---|
|
||||
@@ -53,7 +53,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
| Query | `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`, `list_components` |
|
||||
| Variables | `create_variable`, `set_variable`, `delete_variable` |
|
||||
| Conversion | `import_step`, `export_step` |
|
||||
| Assembly | `assembly_add_component` |
|
||||
| Assembly | `assembly_add_component`, `assembly_add_mate` |
|
||||
| Validation | `validate_part` |
|
||||
|
||||
### Типовой сценарий
|
||||
@@ -72,7 +72,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/ 138 тестов: unit + integration (integration требуют КОМПАС)
|
||||
tests/Kompas.Mcp.Tests/ 156 тестов: 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`). Актуализировано по коду.
|
||||
> Статус: **реализовано и работает** (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`). Актуализировано по коду.
|
||||
> Сопутствующий контекст по 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`), снимок. Все паттерны API5/API7 живут здесь. |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 138 тестов (83 unit + 55 integration). |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 156 тестов (98 unit + 58 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. |
|
||||
|
||||
Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
|
||||
|
||||
@@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
---
|
||||
|
||||
## 5. Карта инструментов (71 инструмент)
|
||||
## 5. Карта инструментов (72 инструмента)
|
||||
|
||||
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`.
|
||||
|
||||
@@ -206,6 +206,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
**Assembly — построение сборок (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`)**
|
||||
- `assembly_add_component` — вставить компонент (.m3d деталь или .a3d подсборка) из файла в активную сборку; задать положение origin (x,y,z мм, мировая СК сборки). Возвращает индекс и имя вставленного компонента. Реализовано через API7: `IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)` → `IPart7` → `IPlacement3D.SetOrigin(x,y,z)` → `UpdatePlacement(true)` → `RebuildModel(true)`. `RequireActiveAssembly` проверяет `doc.DocumentType == ksDocumentAssembly` до обращения к `TopPart`. Откат при неуспехе через `IFeature7.Delete`. DI: `AddSingleton<AssemblyService>()` в `Program.cs`.
|
||||
- `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`, …) — будущие инкременты.
|
||||
|
||||
**Vision — визуальная обратная связь (fallback)**
|
||||
- `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**,
|
||||
@@ -340,7 +341,7 @@ 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):** документы, эскизы (полный набор 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`).
|
||||
**Реализовано (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):** документы, эскизы (полный набор 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`).
|
||||
|
||||
**Следующие приоритеты:**
|
||||
1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D). ⚠️ **Исследовано**: `ksCDimWithVariable` недоступен из внешней автоматизации — см. `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`.
|
||||
@@ -348,7 +349,7 @@ dotnet build -c Release -r win-x64 # сборка
|
||||
3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
|
||||
4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
|
||||
5. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты.
|
||||
6. Сборки — продолжение: сопряжения (`IMates7`), ограничения, авто-позиционирование компонентов; спецификации (`ISpecification`).
|
||||
6. Сборки — продолжение: дополнительные типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`; `coincidence`/`distance` реализованы); авто-позиционирование компонентов; спецификации (`ISpecification`).
|
||||
7. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
|
||||
|
||||
---
|
||||
|
||||
@@ -184,4 +184,7 @@ stateful-сессии? Пока полагаемся на последовате
|
||||
**Решено.** Новый инструмент `hole_conic` — коническое (конусное) отверстие, сужающееся вглубь. Параметры: `x,y,z`, `diameter` (диаметр у основания), `conicAngle` (угол конуса, 0<angle<180), `depth=0`, `throughAll=false`. Реализован через тот же `HoleCore`-паттерн (`configure`-колбэк): `HoleType=ksHTConic` + configure устанавливает `(IConicHoleParameters)hole.HoleParameters` (`ConicType=ksCNAngle`, `ConicAngle`). Завершает набор типов отверстий: простое (`hole`), цековка (`hole_counterbore`), зенковка (`hole_countersink`), коническое (`hole_conic`). Итог: **70 инструментов, 119 тестов (69 unit + 50 integration).**
|
||||
|
||||
### ✅ Вставка компонента в сборку — реализована (начало класса «Сборки»)
|
||||
**Решено.** Новый инструмент `assembly_add_component(filePath, x, y, z)` — первый инструмент, который *пишет* в сборку (ранее `list_components` только читал). Сервис `AssemblyService` (`src/Kompas.Mcp.Core/Assemblies/`), инструменты — `AssemblyTools`. Паттерн API7: `IParts7.AddFromFile` → `IPlacement3D.SetOrigin` → `UpdatePlacement` → `RebuildModel(true)`. Ключевые тонкости: `UpdatePlacement` возвращает FALSE для вручную позиционируемого компонента — не ошибка; зафиксированный первый компонент двигается через `SetOrigin+RebuildModel` без разфиксации; `RequireActiveAssembly` проверяет тип документа до `TopPart`. Откат при неуспехе через `IFeature7.Delete`. Итог: **71 инструмент, 138 тестов (83 unit + 55 integration).** Следующий шаг: сопряжения (`IMates7`), ограничения.
|
||||
**Решено.** Новый инструмент `assembly_add_component(filePath, x, y, z)` — первый инструмент, который *пишет* в сборку (ранее `list_components` только читал). Сервис `AssemblyService` (`src/Kompas.Mcp.Core/Assemblies/`), инструменты — `AssemblyTools`. Паттерн API7: `IParts7.AddFromFile` → `IPlacement3D.SetOrigin` → `UpdatePlacement` → `RebuildModel(true)`. Ключевые тонкости: `UpdatePlacement` возвращает FALSE для вручную позиционируемого компонента — не ошибка; зафиксированный первый компонент двигается через `SetOrigin+RebuildModel` без разфиксации; `RequireActiveAssembly` проверяет тип документа до `TopPart`. Откат при неуспехе через `IFeature7.Delete`.
|
||||
|
||||
### ✅ Сопряжения в сборках — реализованы (coincidence/distance)
|
||||
**Решено (инкремент 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`).
|
||||
|
||||
@@ -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 (71 инструмент)</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 (72 инструмента)</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 · навык kompas-3d · 71 инструмент · 138 тестов</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 · навык kompas-3d · 72 инструмента · 156 тестов</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">71 инструмент, отдаются по MCP-протоколу</p>
|
||||
<p class="meta">72 инструмента, отдаются по 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>
|
||||
@@ -309,8 +309,8 @@
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Query</code>: get_part_info · get_bounding_box · list_faces · list_edges · list_components</span></li>
|
||||
<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)</span></li>
|
||||
<li><span class="mark wip">→</span><span>пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · сопряжения в сборках</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><span class="mark wip">→</span><span>пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -321,7 +321,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">Итого 138 тестов зелёных (83 unit + 55 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 156 тестов зелёных (98 unit + 58 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>
|
||||
@@ -348,8 +348,9 @@
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">draft · готово ✓</div><h4>Уклон</h4><p><b>draft</b> ✓ (API5 <code>ksInclineDefinition</code>, o3d_incline=42) · 64 инструмента · 115 тестов</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет D start · готово ✓</div><h4>Переменные модели</h4><p><b>create_variable</b> · <b>set_variable</b> · <b>delete_variable</b> (API5 VariableService) · list_variables исправлен · 67 инструментов</p></div>
|
||||
<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) · 71 инструмент · 138 тестов</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе</div><h4>пакет E продолжение</h4><p>вспомогательная геометрия (ось, угловая плоскость) · сопряжения в сборках · пакет D параметрика — параметрические эскизы недоступны через COM API</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">далее · в работе</div><h4>пакет E продолжение</h4><p>вспомогательная геометрия (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# Дизайн: сопряжение компонентов сборки (assembly_add_mate) через API7
|
||||
|
||||
**Дата:** 2026-05-27
|
||||
**Статус:** реализовано и проверено (2 интеграционных + 14 unit, всего 154); ревью Codex спека (8) и реализации (9) учтено
|
||||
|
||||
## Правки по ревью реализации Codex (9 замечаний)
|
||||
|
||||
- **#1 `Valid==false` → ошибка** (а не «успех с valid=false»): после Update+Rebuild проверяем
|
||||
`mate.Valid`, иначе бросаем (срабатывает откат). `AddedMate.Valid` теперь всегда true при успехе.
|
||||
Это же косвенно ловит грани одного компонента (#5) — мат выродится.
|
||||
- **#6/#7 distance `value` > 0** (не ≥0): нулевой зазор вырождается в coincidence →
|
||||
`RequireMateValue` требует строго `> 0`; unit-кейс `value=0` бросает.
|
||||
- **#8 негативный интеграционный тест**: точка в пустоте → грань не найдена → ошибка.
|
||||
- **#2 откат best-effort** оставлен намеренно (исходное исключение важнее ошибки отката; паттерн
|
||||
`HoleService`). **#3** `mates.Add ?? throw` до try корректно (null = ничего не добавлено).
|
||||
**#4** «первый IFace» оставлен (консистентно с HoleService/move_face). **#9** точечный release
|
||||
RCW отклонён (конвенция v2-2).
|
||||
|
||||
## Решения по ревью Codex (важные правки к дизайну ниже)
|
||||
|
||||
- **Scope урезан до `coincidence` + `distance`** (#6, #5): только эти два типа геометрически
|
||||
подтверждены спайком → только они в публичном `MateType`/`Parse`. Остальные (parallel/
|
||||
perpendicular/concentric/angle/tangency) и `Alignment`-параметр — будущие инкременты с тестами.
|
||||
- **Валидация value по типу** (#1): `Mates.RequiresValue` → distance требует `value` конечный и
|
||||
`≥ 0`; для типов без value параметр игнорируется (дефолт 0). Валидатор
|
||||
`AssemblyValidation.RequireMateValue(requiresValue, value)`.
|
||||
- **Полный откат** (#3): весь участок после `mates.Add` — в try/catch; при ЛЮБОЙ ошибке (BaseObject,
|
||||
ParamValue, Update=FALSE, RebuildModel=FALSE) сопряжение удаляется через `mate.Owner?.Delete()`
|
||||
(`IFeature7`, как у компонента в инкременте 1).
|
||||
- **Возврат результата** (#8b): `AddedMate { Type: string, Valid: bool }` — для отчёта инструмента и
|
||||
ассертов теста (`Valid`).
|
||||
- **Усиленный тест** (#2): явно `bbBefore ≈ 120`; после mate — `Valid==true`, размеры каждого
|
||||
компонента (`SizeX≈20`) неизменны, ширина сборки изменилась.
|
||||
- **RCW** (#7): точечный release НЕ вводим (конвенция v2-2, как в инкременте 1/HoleService); все
|
||||
COM-вызовы — внутри `dispatcher.InvokeAsync` (уже так). Долг зафиксирован в OPEN_QUESTIONS v2-2.
|
||||
- **Выбор грани** (#8a): паттерн «первый IFace» (как в HoleService/move_face); в описании
|
||||
инструмента — «точка в СЕРЕДИНЕ грани» (на ребре/углу хит неоднозначен).
|
||||
|
||||
## Цель
|
||||
|
||||
Веха СБОРКИ, инкремент 2. После вставки компонентов (`assembly_add_component`, инкремент 1) —
|
||||
**сопряжения**: наложить связь между гранями двух компонентов (совпадение / на расстоянии),
|
||||
чтобы решатель сборки спозиционировал компоненты относительно друг друга. Это вторая
|
||||
половина минимально полезного цикла сборки (вставить → связать).
|
||||
|
||||
## Спайк: механизм подтверждён вживую (НЕ разучивать)
|
||||
|
||||
Спайк (`AssemblyMateSpikeTests`, оба теста зелёные) подтвердил полный путь API7:
|
||||
|
||||
```
|
||||
IPart7 top = (IPart7)doc3d.TopPart; // активная сборка
|
||||
IMateConstraints3D mates = top.MateConstraints; // COM-свойство (не QI)
|
||||
IMateConstraint3D mate = mates.Add(MateConstraintType); // напр. mc_Coincidence=0 / mc_Distance=5
|
||||
mate.BaseObject1 = (IModelObject)face1; // грань компонента 1
|
||||
mate.BaseObject2 = (IModelObject)face2; // грань компонента 2
|
||||
if (distance|angle) mate.ParamValue = value; // зазор (мм) / угол (°)
|
||||
bool ok = mate.Update(); // наложить сопряжение
|
||||
top.RebuildModel(true); // применить к геометрии
|
||||
```
|
||||
|
||||
**Выбор граней компонентов:** `top.FindObjectsByPoint(x, y, z, FirstLevel=false)` — `FirstLevel=false`
|
||||
заставляет спуститься В компоненты и вернуть их грань в **контексте сборки** (мировые координаты с
|
||||
учётом placement компонента). Это именно тот `IModelObject`, который нужен решателю сопряжений.
|
||||
|
||||
**Проверенные результаты (2 куба 20³, A в нуле x[0,20], B со сдвигом +100 x[100,120]):**
|
||||
- **Совпадение** (`mc_Coincidence`) +X грани A (20,10,10) с −X гранью B (100,10,10): `Update=True`,
|
||||
`Valid=True`, `Alignment=ksMCAlignmentClosest` (дефолт). Грани сошлись заподлицо → ширина сборки
|
||||
по X **120 → 40** (кубы соприкасаются). Решатель подвинул A (к B), не B.
|
||||
- **Расстояние** (`mc_Distance`, `ParamValue=30`) тех же граней: `Update=True`, `Valid=True`. Грани
|
||||
встали в 30 мм → ширина сборки по X **120 → 70** (20+30+20).
|
||||
|
||||
**Тонкости (эмпирика):**
|
||||
- `Alignment` НЕ выставляем — дефолт работает (`ksMCAlignmentClosest` для совпадения,
|
||||
`ksMCAlignmentUnknown` для расстояния). Параметр выравнивания — расширение позже (YAGNI).
|
||||
- Решатель может двигать **любой** компонент (в т.ч. первый, «зафиксированный»). Поэтому результат
|
||||
проверяем по **ширине габарита** (грани сблизились), а не по тому, какой компонент сдвинулся.
|
||||
- `mates.Add` возвращает `IMateConstraint3D`; результат `Update()` проверяем (FALSE = не наложилось).
|
||||
После — `RebuildModel(true)` (результат проверяем, как в инкременте 1).
|
||||
|
||||
## Архитектура
|
||||
|
||||
Метод в существующем `AssemblyService` (он уже про сборки). Свой enum `MateType` (паттерн
|
||||
`BasePlane`/`CoordinateAxis`) маппится на КОМПАС `MateConstraintType` — COM-константы не текут в
|
||||
Host/тесты.
|
||||
|
||||
- `src/Kompas.Mcp.Core/Assemblies/MateType.cs` — enum `MateType` + static `Mates`
|
||||
(`ToConstraintType`, `Parse(string)` англ./рус., `RequiresValue`).
|
||||
- `AssemblyService.AddMateAsync(MateType, x1,y1,z1, x2,y2,z2, value, ct)` — реализация.
|
||||
- Инструмент `assembly_add_mate` в `AssemblyTools` (тип сопряжения строкой → `Mates.Parse`).
|
||||
|
||||
## MCP-инструмент
|
||||
|
||||
| Инструмент | Параметры | Поведение |
|
||||
|---|---|---|
|
||||
| `assembly_add_mate` | `mateType: string`, `x1,y1,z1`, `x2,y2,z2`, `value=0` | Наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам (x1..z1) и (x2..z2). `mateType`: coincidence / distance / parallel / perpendicular / concentric / angle / tangency. Для distance — `value` зазор (мм), для angle — `value` угол (°). |
|
||||
|
||||
Контракт: активный документ — сборка (`RequireActiveAssembly`, уже есть); обе точки должны попасть
|
||||
на грани компонентов. **Гарантированно валидированы геометрией: coincidence, distance.** Остальные
|
||||
типы маппятся и вызываемы через тот же путь Add+BaseObject+Update, но не покрыты регрессионным
|
||||
тестом по габариту (требуют иной геометрии/проверки) — помечены в описании инструмента.
|
||||
|
||||
## Валидация (чистые static, unit-тест)
|
||||
|
||||
`Mates`:
|
||||
- `Parse(string)` — англ./рус. → `MateType`; иначе `ArgumentException` со списком допустимых.
|
||||
- `ToConstraintType(MateType)` — → `MateConstraintType`.
|
||||
- `RequiresValue(MateType)` — true для Distance/Angle.
|
||||
|
||||
В сервисе: `RequireFinitePosition` для обеих точек (уже есть в `AssemblyValidation`); если
|
||||
`RequiresValue` — проверить `double.IsFinite(value)` (новый чистый валидатор
|
||||
`AssemblyValidation.RequireFiniteValue`).
|
||||
|
||||
## Реализация
|
||||
|
||||
`AddMateCore`: валидация точек (и value, если нужен) → `RequireActiveAssembly` →
|
||||
`FindFaceInAssembly` (FirstLevel=false) для обеих точек (понятная ошибка, если грань не найдена) →
|
||||
`mates.Add(Mates.ToConstraintType(type))` → `BaseObject1/2` → `ParamValue` (если нужен) →
|
||||
`Update()` (FALSE → откат сопряжения через `IFeature7.Delete` + ошибка) → `RebuildModel(true)`
|
||||
(FALSE → ошибка). Транзитные RCW не освобождаем точечно (долг v2-2, как везде).
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Unit (`MateTests` или в `AssemblyValidationTests`)
|
||||
- `Mates.Parse` — англ. и рус. синонимы → верный `MateType`; бросает на мусоре.
|
||||
- `Mates.ToConstraintType` — каждый `MateType` → ожидаемый `MateConstraintType`.
|
||||
- `Mates.RequiresValue` — true для Distance/Angle, false для прочих.
|
||||
- `AssemblyValidation.RequireFiniteValue` — бросает на NaN/Infinity.
|
||||
|
||||
### Integration (`AssemblyMateTests`)
|
||||
Паттерн: построить куб 20³ → сохранить → создать сборку → вставить 2 компонента (A в нуле, B +100)
|
||||
→ сопряжение → проверить ширину габарита → `finally CloseAsync`.
|
||||
1. **Совпадение** граней A(+X)/B(−X): ширина по X **120 → ~40** (соприкосновение).
|
||||
2. **Расстояние 30**: ширина по X **120 → ~70** (зазор 30).
|
||||
(Эти два сценария = промотированный спайк; спайк-файл удаляется.)
|
||||
|
||||
## Дальнейшее (вне спека)
|
||||
- Выравнивание (`Alignment`), типы concentric/angle/parallel с геометрической проверкой.
|
||||
- Перемещение/фиксация/массив компонентов.
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Kompas.Mcp.Core.Assemblies;
|
||||
|
||||
/// <summary>Результат наложения сопряжения между компонентами сборки.</summary>
|
||||
public sealed record AddedMate
|
||||
{
|
||||
/// <summary>Тип наложенного сопряжения (как был запрошен).</summary>
|
||||
public required MateType Type { get; init; }
|
||||
|
||||
/// <summary>true — сопряжение успешно построено и не вырождено после перестроения.</summary>
|
||||
public required bool Valid { get; init; }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Runtime.Versioning;
|
||||
using Kompas.Mcp.Core.Threading;
|
||||
using Kompas6Constants;
|
||||
using Kompas6Constants3D;
|
||||
using KompasAPI7;
|
||||
|
||||
namespace Kompas.Mcp.Core.Assemblies;
|
||||
@@ -79,6 +80,81 @@ public sealed class AssemblyService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Наложить сопряжение <paramref name="type"/> между гранями двух компонентов активной сборки,
|
||||
/// выбранными по мировым точкам (<paramref name="x1"/>,<paramref name="y1"/>,<paramref name="z1"/>)
|
||||
/// и (<paramref name="x2"/>,<paramref name="y2"/>,<paramref name="z2"/>). Для расстояния
|
||||
/// <paramref name="value"/> — зазор (мм). Решатель спозиционирует компоненты; результат — статус
|
||||
/// построенного сопряжения.
|
||||
/// </summary>
|
||||
public Task<AddedMate> AddMateAsync(MateType type, double x1, double y1, double z1,
|
||||
double x2, double y2, double z2, double value, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddMateCore(type, x1, y1, z1, x2, y2, z2, value), ct);
|
||||
|
||||
private AddedMate AddMateCore(MateType type, double x1, double y1, double z1,
|
||||
double x2, double y2, double z2, double value)
|
||||
{
|
||||
AssemblyValidation.RequireFinitePosition(x1, y1, z1);
|
||||
AssemblyValidation.RequireFinitePosition(x2, y2, z2);
|
||||
AssemblyValidation.RequireMateValue(Mates.RequiresValue(type), value);
|
||||
|
||||
var top = RequireActiveAssembly();
|
||||
var face1 = FindFaceInAssembly(top, x1, y1, z1)
|
||||
?? throw new InvalidOperationException(
|
||||
$"В точке ({x1},{y1},{z1}) не найдена грань компонента. Укажите точку в середине грани (model_snapshot).");
|
||||
var face2 = FindFaceInAssembly(top, x2, y2, z2)
|
||||
?? throw new InvalidOperationException(
|
||||
$"В точке ({x2},{y2},{z2}) не найдена грань компонента. Укажите точку в середине грани (model_snapshot).");
|
||||
|
||||
var mates = top.MateConstraints
|
||||
?? throw new InvalidOperationException("Не удалось получить коллекцию сопряжений сборки (MateConstraints).");
|
||||
var mate = mates.Add(Mates.ToConstraintType(type))
|
||||
?? throw new InvalidOperationException("MateConstraints.Add вернул null.");
|
||||
|
||||
try
|
||||
{
|
||||
mate.BaseObject1 = (IModelObject)face1;
|
||||
mate.BaseObject2 = (IModelObject)face2;
|
||||
if (Mates.RequiresValue(type))
|
||||
mate.ParamValue = value;
|
||||
|
||||
if (!mate.Update())
|
||||
throw new InvalidOperationException(
|
||||
"Update сопряжения вернул FALSE — связь не наложена (несовместимые грани/значение?).");
|
||||
if (!top.RebuildModel(true))
|
||||
throw new InvalidOperationException("RebuildModel вернул FALSE — сборка не перестроена после сопряжения.");
|
||||
if (!mate.Valid)
|
||||
throw new InvalidOperationException(
|
||||
"Сопряжение построено, но вырождено (Valid=false) — вероятно, грани несовместимы " +
|
||||
"или принадлежат одному компоненту. Проверьте выбор граней.");
|
||||
|
||||
return new AddedMate { Type = type, Valid = true };
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Полный откат: при ЛЮБОЙ ошибке после Add удаляем сопряжение, чтобы не оставить «сироту».
|
||||
try { mate.Owner?.Delete(); top.RebuildModel(true); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static IFace? FindFaceInAssembly(IPart7 top, double x, double y, double z)
|
||||
{
|
||||
// FirstLevel=false — спускаемся в компоненты, чтобы получить их грани в контексте сборки.
|
||||
var hit = top.FindObjectsByPoint(x, y, z, false);
|
||||
switch (hit)
|
||||
{
|
||||
case IFace f: return f;
|
||||
case object[] arr:
|
||||
foreach (var o in arr) if (o is IFace face) return face;
|
||||
return null;
|
||||
case System.Collections.IEnumerable en:
|
||||
foreach (var o in en) if (o is IFace face) return face;
|
||||
return null;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Откатить вставку: удалить компоненты, появившиеся сверх <paramref name="beforeCount"/> (обычно
|
||||
/// один — только что вставленный). Best-effort, но с верификацией: компонент удаляется через
|
||||
|
||||
@@ -29,10 +29,24 @@ public static class AssemblyValidation
|
||||
return Path.GetFullPath(trimmed);
|
||||
}
|
||||
|
||||
/// <summary>Проверить, что координаты вставки конечны (отсекает NaN/Infinity).</summary>
|
||||
/// <summary>Проверить, что координаты конечны (отсекает NaN/Infinity).</summary>
|
||||
public static void RequireFinitePosition(double x, double y, double z)
|
||||
{
|
||||
if (!double.IsFinite(x) || !double.IsFinite(y) || !double.IsFinite(z))
|
||||
throw new ArgumentException("Координаты вставки компонента должны быть конечными числами (не NaN/Infinity).");
|
||||
throw new ArgumentException("Координаты должны быть конечными числами (не NaN/Infinity).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверить числовое значение сопряжения. Если значение требуется (<paramref name="requiresValue"/>,
|
||||
/// напр. расстояние) — оно должно быть конечным и строго положительным (зазор 0 вырождается в
|
||||
/// совпадение — для него есть отдельный тип).
|
||||
/// </summary>
|
||||
public static void RequireMateValue(bool requiresValue, double value)
|
||||
{
|
||||
if (!requiresValue) return; // для типов без параметра значение игнорируется
|
||||
if (!double.IsFinite(value) || value <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value),
|
||||
"Значение сопряжения (расстояние) должно быть конечным и > 0 " +
|
||||
"(для нулевого зазора используйте тип coincidence).");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Kompas6Constants3D;
|
||||
|
||||
namespace Kompas.Mcp.Core.Assemblies;
|
||||
|
||||
/// <summary>
|
||||
/// Дружелюбный тип сопряжения (обёртка над <see cref="MateConstraintType"/>). Пока поддержаны только
|
||||
/// геометрически проверенные типы — совпадение и расстояние; остальные (параллельность, соосность,
|
||||
/// угол и т.д.) добавятся в следующих инкрементах вместе с тестами.
|
||||
/// </summary>
|
||||
public enum MateType
|
||||
{
|
||||
/// <summary>Совпадение (грани лежат на одной плоскости, заподлицо).</summary>
|
||||
Coincidence,
|
||||
/// <summary>На расстоянии (постоянный зазор между гранями, мм).</summary>
|
||||
Distance,
|
||||
}
|
||||
|
||||
/// <summary>Маппинг дружелюбного типа сопряжения ↔ перечисление API КОМПАС и разбор строки.</summary>
|
||||
public static class Mates
|
||||
{
|
||||
/// <summary>Сопряжения, которым нужно числовое значение (<c>ParamValue</c>): расстояние.</summary>
|
||||
public static bool RequiresValue(MateType type) => type is MateType.Distance;
|
||||
|
||||
public static MateConstraintType ToConstraintType(MateType type) => type switch
|
||||
{
|
||||
MateType.Coincidence => MateConstraintType.mc_Coincidence,
|
||||
MateType.Distance => MateConstraintType.mc_Distance,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Неизвестный тип сопряжения."),
|
||||
};
|
||||
|
||||
/// <summary>Разобрать строку типа сопряжения (англ./рус.) в <see cref="MateType"/>.</summary>
|
||||
public static MateType Parse(string value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"coincidence" or "coincident" or "совпадение" => MateType.Coincidence,
|
||||
"distance" or "расстояние" => MateType.Distance,
|
||||
_ => throw new ArgumentException(
|
||||
$"Неизвестный/неподдерживаемый тип сопряжения '{value}'. Сейчас поддержаны: " +
|
||||
"coincidence (совпадение), distance (расстояние).", nameof(value)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,26 @@ public sealed class DocumentService
|
||||
=> _dispatcher.InvokeAsync(() => WithActive(d =>
|
||||
d.Close(save ? DocumentCloseOptions.kdSaveChanges : DocumentCloseOptions.kdDoNotSaveChanges)), ct);
|
||||
|
||||
/// <summary>
|
||||
/// Закрыть ВСЕ открытые документы без сохранения; возвращает число закрытых. Применяется для
|
||||
/// очистки между тестами, чтобы не накапливались вкладки-пустышки. Закрываем активный документ
|
||||
/// в цикле (после закрытия активным становится следующий) с защитой от зацикливания: если очередной
|
||||
/// документ закрыть не удалось, прекращаем, чтобы не крутить впустую на одном и том же.
|
||||
/// </summary>
|
||||
public Task<int> CloseAllAsync(CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
int closed = 0;
|
||||
for (int guard = 0; guard < 256; guard++)
|
||||
{
|
||||
var doc = _session.Application.ActiveDocument;
|
||||
if (doc is null) break;
|
||||
try { doc.Close(DocumentCloseOptions.kdDoNotSaveChanges); closed++; }
|
||||
finally { ComHelper.Release(doc); }
|
||||
}
|
||||
return closed;
|
||||
}, ct);
|
||||
|
||||
private void WithActive(Action<IKompasDocument> action)
|
||||
=> WithActive(d => { action(d); return true; });
|
||||
|
||||
|
||||
@@ -24,4 +24,26 @@ public sealed class AssemblyTools(KompasSession session, AssemblyService assembl
|
||||
var c = await assembly.AddComponentAsync(filePath, x, y, z);
|
||||
return $"Компонент '{c.Name}' вставлен в сборку, индекс {c.Index}, позиция ({x}, {y}, {z}).";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "assembly_add_mate")]
|
||||
[Description("Наложить сопряжение между гранями ДВУХ компонентов активной сборки, выбранными по " +
|
||||
"мировым точкам (x1,y1,z1) и (x2,y2,z2) — точки в СЕРЕДИНЕ граней (мм). mateType: " +
|
||||
"'coincidence' (совпадение, грани заподлицо) или 'distance' (на расстоянии value мм между гранями). " +
|
||||
"Решатель спозиционирует компоненты. Сделайте model_snapshot/describe_face, прикиньте точки на гранях.")]
|
||||
public async Task<string> AddMate(
|
||||
[Description("Тип сопряжения: coincidence | distance")] string mateType,
|
||||
[Description("X точки на грани компонента 1, мм")] double x1,
|
||||
[Description("Y точки на грани компонента 1, мм")] double y1,
|
||||
[Description("Z точки на грани компонента 1, мм")] double z1,
|
||||
[Description("X точки на грани компонента 2, мм")] double x2,
|
||||
[Description("Y точки на грани компонента 2, мм")] double y2,
|
||||
[Description("Z точки на грани компонента 2, мм")] double z2,
|
||||
[Description("Зазор для distance, мм (для coincidence не нужен)")] double value = 0)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var type = Mates.Parse(mateType);
|
||||
var m = await assembly.AddMateAsync(type, x1, y1, z1, x2, y2, z2, value);
|
||||
var valueText = Mates.RequiresValue(type) ? $", значение {value} мм" : "";
|
||||
return $"Сопряжение '{type}'{valueText} наложено (valid={m.Valid}).";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,47 @@ public sealed class AssemblyValidationTests
|
||||
[Fact]
|
||||
public void RequireFinitePosition_accepts_finite()
|
||||
=> Assert.Null(Record.Exception(() => AssemblyValidation.RequireFinitePosition(50, -10, 0)));
|
||||
|
||||
// ── Сопряжения (Mates) ───────────────────────────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData("coincidence", MateType.Coincidence)]
|
||||
[InlineData("совпадение", MateType.Coincidence)]
|
||||
[InlineData("DISTANCE", MateType.Distance)]
|
||||
[InlineData(" расстояние ", MateType.Distance)]
|
||||
public void Mates_Parse_maps_known(string text, MateType expected)
|
||||
=> Assert.Equal(expected, Mates.Parse(text));
|
||||
|
||||
[Theory]
|
||||
[InlineData("parallel")] // пока не поддержан
|
||||
[InlineData("сопряжение")]
|
||||
[InlineData("")]
|
||||
public void Mates_Parse_throws_on_unknown(string text)
|
||||
=> Assert.Throws<ArgumentException>(() => Mates.Parse(text));
|
||||
|
||||
// Корректность ToConstraintType проверяется интеграционно (coincidence даёт прилегание,
|
||||
// distance — зазор), а не unit-ассертом: КОМПАС-enum встроен (EmbedInteropTypes) и не виден
|
||||
// тестовому проекту через границу сборки.
|
||||
|
||||
[Theory]
|
||||
[InlineData(MateType.Distance, true)]
|
||||
[InlineData(MateType.Coincidence, false)]
|
||||
public void Mates_RequiresValue(MateType type, bool expected)
|
||||
=> Assert.Equal(expected, Mates.RequiresValue(type));
|
||||
|
||||
[Fact]
|
||||
public void RequireMateValue_ignores_value_when_not_required()
|
||||
=> Assert.Null(Record.Exception(() => AssemblyValidation.RequireMateValue(false, double.NaN)));
|
||||
|
||||
[Theory]
|
||||
[InlineData(double.NaN)]
|
||||
[InlineData(double.PositiveInfinity)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)] // нулевой зазор вырождается в coincidence → ошибка
|
||||
public void RequireMateValue_throws_on_bad_required_value(double value)
|
||||
=> Assert.Throws<ArgumentOutOfRangeException>(() => AssemblyValidation.RequireMateValue(true, value));
|
||||
|
||||
[Fact]
|
||||
public void RequireMateValue_accepts_positive_required_value()
|
||||
=> Assert.Null(Record.Exception(() => AssemblyValidation.RequireMateValue(true, 30)));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
using Kompas.Mcp.Core.Assemblies;
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
using Kompas.Mcp.Core.Query;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: сопряжения компонентов сборки (API7 IMateConstraints3D).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class AssemblyMateTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly AssemblyService _assembly;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public AssemblyMateTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
_assembly = new AssemblyService(fx.Session, fx.Dispatcher);
|
||||
_query = new QueryService(fx.Session, fx.Dispatcher);
|
||||
}
|
||||
|
||||
private async Task<string> BuildAndSaveCubeAsync(double side)
|
||||
{
|
||||
var path = TestPaths.NewFile(".m3d");
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddRectangleAsync(s, 0, 0, side, side);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: side);
|
||||
await _modeler.RebuildAsync();
|
||||
await _docs.SaveAsAsync(path);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>Создать сборку из двух кубов 20³: A в нуле (world x[0,20]), B со сдвигом +100 (x[100,120]).</summary>
|
||||
private async Task<string> SetupTwoCubesAsync()
|
||||
{
|
||||
var cube = await BuildAndSaveCubeAsync(20);
|
||||
await _docs.CreateAsync(KompasDocumentType.Assembly);
|
||||
await _assembly.AddComponentAsync(cube, 0, 0, 0);
|
||||
await _assembly.AddComponentAsync(cube, 100, 0, 0);
|
||||
return cube;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Coincidence_mate_brings_faces_flush()
|
||||
{
|
||||
await SetupTwoCubesAsync();
|
||||
try
|
||||
{
|
||||
var bbBefore = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(bbBefore.MaxX - bbBefore.MinX, 118, 122); // 2 куба врозь ≈ 120
|
||||
|
||||
// Совпадение +X грани A (20,10,10) с −X гранью B (100,10,10).
|
||||
var m = await _assembly.AddMateAsync(MateType.Coincidence, 20, 10, 10, 100, 10, 10, value: 0);
|
||||
Assert.True(m.Valid, "Сопряжение должно быть валидным.");
|
||||
|
||||
var bbAfter = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(bbAfter.MaxX - bbAfter.MinX, 38, 42); // кубы соприкоснулись ≈ 40
|
||||
|
||||
// Размеры самих компонентов не изменились (двигаются, не деформируются).
|
||||
var comps = await _query.ListComponentsAsync();
|
||||
Assert.Equal(2, comps.Count);
|
||||
Assert.All(comps, c => Assert.InRange(c.SizeX, 19, 21));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Distance_mate_holds_faces_apart()
|
||||
{
|
||||
await SetupTwoCubesAsync();
|
||||
try
|
||||
{
|
||||
var bbBefore = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(bbBefore.MaxX - bbBefore.MinX, 118, 122);
|
||||
|
||||
// Расстояние 30 между +X гранью A и −X гранью B → ширина 20+30+20 = 70.
|
||||
var m = await _assembly.AddMateAsync(MateType.Distance, 20, 10, 10, 100, 10, 10, value: 30);
|
||||
Assert.True(m.Valid, "Сопряжение должно быть валидным.");
|
||||
|
||||
var bbAfter = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(bbAfter.MaxX - bbAfter.MinX, 66, 74); // ≈ 70
|
||||
|
||||
var comps = await _query.ListComponentsAsync();
|
||||
Assert.All(comps, c => Assert.InRange(c.SizeX, 19, 21));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Mate_with_point_off_any_face_throws()
|
||||
{
|
||||
await SetupTwoCubesAsync();
|
||||
try
|
||||
{
|
||||
// Вторая точка — в пустоте (далеко от обоих кубов): грань не найдена → понятная ошибка.
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => _assembly.AddMateAsync(MateType.Coincidence, 20, 10, 10, 1000, 1000, 1000, value: 0));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: вставка компонента в сборку (API7 IParts7.AddFromFile + Placement).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class AssemblyTests
|
||||
public sealed class AssemblyTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly AssemblyService _assembly;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public AssemblyTests(KompasFixture fx)
|
||||
public AssemblyTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -3,11 +3,11 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: подключение к КОМПАС. Требует установленный КОМПАС-3D.</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class ConnectionTests
|
||||
public sealed class ConnectionTests : IntegrationTestBase
|
||||
{
|
||||
private readonly KompasFixture _fx;
|
||||
|
||||
public ConnectionTests(KompasFixture fx) => _fx = fx;
|
||||
public ConnectionTests(KompasFixture fx) : base(fx) => _fx = fx;
|
||||
|
||||
[Fact]
|
||||
public void Connects_to_kompas()
|
||||
|
||||
@@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: импорт/экспорт STEP и обход компонентов сборки.</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class ConversionTests
|
||||
public sealed class ConversionTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly ConversionService _conv;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public ConversionTests(KompasFixture fx)
|
||||
public ConversionTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: жизненный цикл документов.</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class DocumentTests
|
||||
public sealed class DocumentTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
|
||||
public DocumentTests(KompasFixture fx)
|
||||
public DocumentTests(KompasFixture fx) : base(fx)
|
||||
=> _docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: прямое редактирование — перемещение грани (FaceMover).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class FaceEditTests
|
||||
public sealed class FaceEditTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly FaceEditService _faceEdit;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public FaceEditTests(KompasFixture fx)
|
||||
public FaceEditTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: формообразующие операции пакетов B и C (массивы, зеркало).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class FeatureOpsTests
|
||||
public sealed class FeatureOpsTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public FeatureOpsTests(KompasFixture fx)
|
||||
public FeatureOpsTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: операция «Отверстие» (API7 IHoles3D/IHole3D).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class HoleTests
|
||||
public sealed class HoleTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly HoleService _holes;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public HoleTests(KompasFixture fx)
|
||||
public HoleTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: структурное «зрение» — дерево, топология, drill-down, измерения.</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class InspectionTests
|
||||
public sealed class InspectionTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly ModelInspectionService _inspect;
|
||||
|
||||
public InspectionTests(KompasFixture fx)
|
||||
public InspectionTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс интеграционных тестов: после КАЖДОГО теста закрывает все открытые документы
|
||||
/// КОМПАС без сохранения, чтобы не копились вкладки-пустышки (и чтобы упавший Assert не оставлял
|
||||
/// открытый документ). xUnit создаёт новый экземпляр тест-класса на каждый тест-метод, поэтому
|
||||
/// <see cref="DisposeAsync"/> вызывается per-test — это и есть точка очистки.
|
||||
/// </summary>
|
||||
public abstract class IntegrationTestBase : IAsyncLifetime
|
||||
{
|
||||
private readonly DocumentService _cleanupDocs;
|
||||
|
||||
protected IntegrationTestBase(KompasFixture fx)
|
||||
=> _cleanupDocs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
public Task DisposeAsync() => _cleanupDocs.CloseAllAsync();
|
||||
}
|
||||
@@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: построение 3D (эскиз → выдавливание) и снимок.</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class ModelingTests
|
||||
public sealed class ModelingTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly SnapshotService _snap;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public ModelingTests(KompasFixture fx)
|
||||
public ModelingTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: массово-центровочные характеристики (get_part_info).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class QueryTests
|
||||
public sealed class QueryTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public QueryTests(KompasFixture fx)
|
||||
public QueryTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: новые примитивы эскиза (профиль → выдавливание → объём).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class SketchPrimitivesTests
|
||||
public sealed class SketchPrimitivesTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public SketchPrimitivesTests(KompasFixture fx)
|
||||
public SketchPrimitivesTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -6,12 +6,12 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: снимок модели в растр («зрение агента»).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class SnapshotTests
|
||||
public sealed class SnapshotTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly SnapshotService _snap;
|
||||
|
||||
public SnapshotTests(KompasFixture fx)
|
||||
public SnapshotTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_snap = new SnapshotService(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: рассечение тела, перемещение тела, объединение (cut→spread→union).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class SolidEditTests
|
||||
public sealed class SolidEditTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
@@ -18,7 +18,7 @@ public sealed class SolidEditTests
|
||||
private readonly QueryService _query;
|
||||
private readonly ValidationService _validation;
|
||||
|
||||
public SolidEditTests(KompasFixture fx)
|
||||
public SolidEditTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: валидация построения (ошибки/перестроение операций).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class ValidationTests
|
||||
public sealed class ValidationTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly ValidationService _validation;
|
||||
|
||||
public ValidationTests(KompasFixture fx)
|
||||
public ValidationTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace Kompas.Mcp.Tests.Integration;
|
||||
/// <summary>Интеграция: переменные модели (параметрика, API5 ksVariable).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class VariableTests
|
||||
public sealed class VariableTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly VariableService _vars;
|
||||
private readonly ModelInspectionService _inspect;
|
||||
|
||||
public VariableTests(KompasFixture fx)
|
||||
public VariableTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
|
||||
Reference in New Issue
Block a user