Files
kompas3d-mcp/docs/ARCHITECTURE.md
T
mikhail 28d2ae3868 feat(inspection): структурное «зрение» модели — осмотр без снимка
Новый слой «модель как данные» (приоритетный над model_snapshot):
- describe_model — структурный «паспорт» детали одним вызовом (габарит,
  МЦХ, тела, сводка топологии, дерево построения с параметрами, переменные)
- list_features / list_bodies / list_variables
- describe_face / describe_edge (drill-down по индексу)
- measure (расстояние/угол между гранями/рёбрами/вершинами)
- model_snapshot понижен до fallback для визуально-пространственных вопросов

Реализация: ModelInspectionService (COM) + чистый InspectionText (рендер,
группировка, детект «импорт без истории») + record-модели; InspectionTools.
Дерево читается через ksPart.GetFeature()->SubFeatureCollection; точный тип
и параметры операций берутся из GetObject()->GetDefinition() (ksFeature.type
отдаёт лишь coarse o3d_entity).

Снимок дерева также включает слои conversion (STEP), editing (move_face/solid)
и validation, развивавшиеся параллельно. Полный прогон: 61 тест зелёный,
43 MCP-инструмента. Документация (README, ARCHITECTURE, OPEN_QUESTIONS,
CLAUDE.md, presentation.html) синхронизирована.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:44:38 +03:00

328 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант)
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection). Актуализировано по коду.
> Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md).
> Имена интерфейсов и перечислений сверены со справкой SDK через `.claude/skills/kompas-sdk-research/kdoc.py` (навык `kompas-sdk-research`).
---
## 1. Назначение и контекст
Строим **MCP-сервер**, который даёт LLM управлять CAD-системой **КОМПАС-3D** (АСКОН):
создавать/открывать документы, строить 2D-геометрию, 3D-детали и сборки, читать/писать
параметры, запускать команды.
Жёсткие рамки, заданные природой API:
- **Только Windows, только x64** — КОМПАС автоматизируется через **COM Automation API**;
разрядность сервера должна совпадать с разрядностью установленного КОМПАС.
- **КОМПАС должен быть установлен и запускаем на той же машине.** Сервер — это **COM-клиент**,
а не самостоятельный геометрический движок. Без работающего экземпляра КОМПАС инструменты
бесполезны.
- Целевая среда — **КОМПАС-3D v24 Home** (LT-редакция); часть возможностей полной редакции
может быть недоступна.
Две сосуществующие версии API (подробнее — `CLAUDE.md`):
| | Точка входа | Namespace (C#) | Когда |
|---|---|---|---|
| **API5** (legacy) | `KompasObject` | `Kompas6API5` | bootstrap-подключение, построение 3D через `ksPart` |
| **API7** (современный) | `IApplication` | `KompasAPI7` | всё новое: документы, параметры, операции |
---
## 2. Выбранный стек
| Решение | Выбор | Обоснование |
|---|---|---|
| Язык / платформа | **.NET 8 (LTS), C#, `win-x64`** | COM-интероп первого класса; SDK поставляет именно C#-примеры и `.tlb` |
| MCP SDK | **`ModelContextProtocol`** (официальный C# SDK) | хостинг через `Microsoft.Extensions.Hosting`, DI, готовый stdio-транспорт |
| Транспорт | **stdio** | локальный клиент (Claude Desktop / IDE) запускает сервер дочерним процессом на машине с КОМПАС — проще и безопаснее всего |
| COM-интероп | COM-references на `.tlb` из `SDK\lib` | строгая типизация интерфейсов API7 (`IApplication`, `IPart7`, …) на этапе компиляции |
| Логи | `Microsoft.Extensions.Logging`**stderr** | **stdout занят протоколом MCP** — туда писать нельзя |
| Сериализация | `System.Text.Json` | штатно в .NET, без зависимостей |
Подключаемые типобиблиотеки (`C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK\lib`):
`kAPI5.tlb`, `kAPI7.tlb`, `ksConstants.tlb`, `ksConstants3D.tlb`, `kAPI2D5COM.tlb`, `kAPI3D5COM.tlb`.
Namespaces: `Kompas6API5`, `KompasAPI7`, `Kompas6Constants`, `Kompas6Constants3D`, `KAPITypes`.
### Почему C#, а не Python
Python (comtypes/pywin32) технически способен управлять COM, но КОМПАС-API активно использует
конструкции, болезненные для динамического интеропа: `out`/`ref`-параметры (например,
`ExecuteKompasCommand(commandID, post, &retval)`), `SAFEARRAY`/динамические массивы
(`ksDynamicArray`, `ksLtVariant`), мосты API5↔API7 (`ksGetApplication7`, `TransferInterface`) и
обязательный STA + насос сообщений. В C# всё это покрыто строго типизированным interop, что
снижает риск и стоимость поддержки.
---
## 3. Модель процесса и потоков (ключевой раздел)
COM-объекты КОМПАС живут в **STA (Single-Threaded Apartment)**, экземпляр приложения —
**единственный** и **однопоточный**. Отсюда — центральное архитектурное ограничение:
- Сервер держит **один выделенный STA-поток**, который **владеет всеми без исключения
COM-вызовами**. Асинхронные обработчики MCP-инструментов (поток-пул) **маршалят** работу в этот
поток через очередь/диспетчер (например, `BlockingCollection<Func<…>>` или собственный
`SynchronizationContext`). Это автоматически **сериализует** доступ к КОМПАС.
- STA-поток крутит **насос сообщений Windows** и периодически вызывает
`IApplication.PumpWaitingMessages()` / при необходимости `EnableTaskAccess(false)` на время
пакетных операций — чтобы диалоги и колбэки КОМПАС не приводили к взаимоблокировке.
```
MCP client ──stdio──> Host (поток-пул, async)
│ ставит задачу в очередь
┌─────────────────────────┐
│ STA-поток (COM owner) │ ← насос сообщений + PumpWaitingMessages
│ KompasObject / IApplication
└─────────────────────────┘
│ COM
КОМПАС-3D.exe
```
### Подключение к КОМПАС
1. Разрешить ProgID. На целевой машине (v24 Home) **проверено**: зарегистрированы
`KOMPAS.Application.7` и `KOMPAS.Application.5`, а `KOMPASLT.*` **отсутствует** — то есть
Home-редакция использует обычные ProgID, а не LT. Порядок попыток:
`KOMPAS.Application.7` (прямой API7) → `KOMPAS.Application.5` (+ `ksGetApplication7()`) →
`KOMPASLT.Application.5` (запасной, для других инсталляций). Выбранный ProgID фиксировать в логе.
2. **Attach** к запущенному экземпляру: `Marshal.GetActiveObject(progId)`.
Если не запущен — **launch**: `Activator.CreateInstance(Type.GetTypeFromProgID(progId))`.
3. Если зашли через API5 — получить современное приложение:
`IApplication appl = (IApplication)kompas.ksGetApplication7();` (через `KOMPAS.Application.7` `IApplication` уже на руках).
4. `appl.Visible = true;` — показать окно КОМПАС; дальше работать через интерфейсы API7.
### Жизненный цикл COM
- Корневые RCW (`KompasObject`, `IApplication`) держим на всё время жизни сервера.
- Временные объекты освобождаем (`Marshal.ReleaseComObject`) в обратном порядке создания.
- `appl.Quit()` вызываем **только если экземпляр запустили мы сами** (флаг «launched-by-us»);
к чужому запущенному КОМПАС не применяем.
---
## 4. Слои решения
Проекты solution `KompasMcp.slnx` (спайк-проекты ConnectSpike/ReconSpike удалены; их механика покрыта тестами):
| Проект | Ответственность |
|---|---|
| **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, измерения), снимок. Все паттерны API5/API7 живут здесь. |
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 60 тестов. |
Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
Архитектурный принцип: **MCP транслирует возможности SDK КОМПАС в общие инструменты** (не под конкретную задачу). Методический слой поверх MCP — навык `.claude/skills/kompas-3d/` (playbook'и, эвристики). Полигон отработки подходов — `usecases/` (в .gitignore).
---
## 5. Карта инструментов (43 инструмента)
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`.
**System**
- `kompas_connect` — attach/launch, возвращает версию и редакцию (`GetSystemVersion`, `ApplicationName`).
- `kompas_set_visible` — показать/скрыть окно (`IApplication.Visible`).
- `kompas_status` — состояние соединения, активный документ.
**Documents** (`IApplication.Documents``IDocuments`)
- `document_create` — тип `part | assembly | drawing | fragment` (`DocumentTypeEnum`: Part=4, Assembly=5, Drawing=1, Fragment=2).
- `document_open` / `document_save` / `document_close` / `document_active`.
**Sketch** (2D-геометрия внутри 3D-эскиза)
- `sketch_create` — на базовой плоскости (`o3d_planeXOY` / `XOZ` / `YOZ`) или на выбранной грани.
- `sketch_add_line` / `sketch_add_circle` / `sketch_add_arc` / `sketch_add_rectangle`.
- `sketch_close` — завершить редактирование.
- Жизненный цикл: `NewEntity(o3d_sketch)``SetPlane(plane|face)``BeginEdit()`
`ksLineSeg` / `ksCircle` / … → `EndEdit()`.
**Features** (3D-операции)
- `extrude_boss` / `extrude_cut` — sketch, depth, direction, endType.
- `revolve_boss` / `revolve_cut` — угол.
- `rebuild` — перестроить деталь.
**Edit** (прямое редактирование)
- `move_face` — сдвинуть грань по мировой точке (x,y,z, мм) на distance мм вдоль нормали; distance>0 — наружу (добавить материал), <0 — внутрь. Работает на импортированной B-rep. Реализовано через API7 `IPart7.FindObjectsByPoint` + `FaceMover`; контейнеры `ISurfaceContainer`/`IModelContainer` получаются COM-QI от `IPart7` в рантайме. Проверено: top_spacer 39.45→41.45 мм.
**Selection / Query**
- `get_part_info` — МЦХ через API5 `ksPart.CalcMassInertiaProperties(ST_MIX_MM|ST_MIX_KG)`.
- `get_bounding_box` — габарит (`ksPart.GetGabarit`).
- `list_faces` / `list_edges` — перечислить грани и рёбра детали с классификацией и мерами.
- `list_components` — перечислить компоненты верхнего уровня сборки (индекс, имя, обозначение, признак детали, габарит) через `TopPart → IParts7`.
**Conversion** (`ConversionTools.cs`, `ConversionService.cs`)
- `import_step` — импорт .step/.stp в новый документ через встроенный конвертер КОМПАС (код `ksConverterFromSTEP=-3`); параметры `type` (assembly|part), `createComponentFiles`.
- `export_step` — экспорт активного 3D-документа в STEP; `format` = auto|ap203|ap214|ap242.
**Inspection — структурный осмотр модели (приоритетный над снимком)**
- `describe_model` — структурный «паспорт» детали одним вызовом: габарит, МЦХ, тела, сводка топологии (грани/рёбра по типам), дерево построения с параметрами, переменные. **Предпочитать перед `model_snapshot`** — не тратит токены изображения.
- `list_features` — дерево построения с параметрами (глубина/радиус/катеты), имена узлов локализованы КОМПАС.
- `list_bodies` — тела детали: тип (твёрдое/поверхность) и число граней.
- `list_variables` — переменные модели (имя/выражение/значение, флаги external/информационная).
- `describe_face` — drill-down грани по индексу: тип, площадь, нормаль, радиус, число рёбер.
- `describe_edge` — drill-down ребра по индексу: тип, длина, смежные грани, концевые вершины.
- `measure` — расстояние и угол между двумя объектами (face|edge|vertex по индексам), единица `ST_MIX_MM`.
Реализовано через `ModelInspectionService` (`src/Kompas.Mcp.Core/Query/ModelInspectionService.cs`),
рендер текста — `InspectionText` (`InspectionText.cs`, юнит-тестируем без COM).
Ключевые API-паттерны: `ksPart.GetFeature()``ksFeatureCollection` (дерево); `BodyCollection()``ksBody`; `VariableCollection()``ksVariable`; `GetMeasurer()``ksMeasurer`.
«Импорт STEP без истории» определяется структурно: `bodyCount > 0 && нет формообразующих операций && features.Count <= bodyCount+1`.
**Vision — визуальная обратная связь (fallback)**
- `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**,
чтобы мультимодальный агент *увидел* промежуточный результат. Использовать для визуально-пространственных
вопросов, когда структурный осмотр (`describe_model`) недостаточен.
- `set_view` — задать ориентацию вида (`iso` / `front` / `top` / `right` / …), zoom-to-fit и режим
отображения (`ShadedWireframe`) перед снимком — иначе снимок берётся со случайного текущего ракурса.
> **Механизм.** `ksDocument3D.RasterFormatParam()` → `IRasterFormatParam` рендерит документ в растр;
> свойство **`resultArrayBytes`** возвращает изображение как **массив байт в память** (без временного
> файла). Альтернатива — экспортные функции `ksSaveAsToRasterFormat[W]`. Полученные байты сервер
> упаковывает в image-контент MCP-ответа (C# SDK `ModelContextProtocol` это поддерживает).
> Параметры: формат (PNG/JPEG/BMP/TIFF), разрешение, цвет/`greyScale`.
> Снимки делать после значимых операций (не после каждой линии) — каждый кадр расходует
> токены изображения в контексте агента. На v24 Home качество/доступность рендера проверить на прототипе.
**Escape hatch (опционально)**
- `run_kompas_command``IApplication.ExecuteKompasCommand(commandID, post)` для команд,
ещё не обёрнутых в типизированные инструменты. Доступность проверять через
`IsKompasCommandEnable`.
### Замечание об API5 vs API7 для 3D-операций
API7 создаёт операции через `IPart7` → (QueryInterface) `IModelContainer``IExtrusions.Add()`
`IExtrusion`. Примеры SDK при этом активно используют **API5-путь** (`ksPart.NewEntity(o3d_…)`
`ksBossExtrusionDefinition.SetSideParam/SetSketch``Create()`), который хорошо документирован.
**Стратегия v1:** предпочитать API7 там, где он покрывает операцию; падать на API5 `ksPart`-путь
там, где он проще/надёжнее (мост — `kompas.TransferInterface`). Выбор фиксировать в `Core`.
---
## 6. Центральный сценарий: цикл «эскиз → операция → эскиз»
Основной пользовательский поток v1 — итеративное параметрическое моделирование.
```
document_create(part)
sketch_create(plane = XOY) ──► sketch_add_circle(...) ──► sketch_close
extrude_boss(sketch, depth=…, dir=dtNormal, end=etBlind) → цилиндр
set_view(iso, fit) → model_snapshot 👁 агент СМОТРИТ на результат и решает следующий шаг
select_face_by_point(x,y,z) ← выбираем верхнюю грань
sketch_create(face = выбранная) ──► sketch_add_circle(...) ──► sketch_close
extrude_cut(sketch, depth=…) → отверстие
model_snapshot 👁 проверка результата
└──────────► повторять: смотрим → новая грань → новый эскиз → новая операция
```
Соответствие API (на STA-потоке, через `Core`):
1. `Document3D()` / `IKompasDocument3D``IPart7` (вершинная деталь, `pTop_Part`).
2. `part.NewEntity(o3d_sketch)``GetDefinition()``SetPlane(базовая_плоскость | грань)`
`Create()`; `BeginEdit()` → рисование примитивов (`ksLineSeg`, `ksCircle`, `ksArcByPoint`, …) →
`EndEdit()`.
3. `part.NewEntity(o3d_bossExtrusion)``ksBossExtrusionDefinition`
`SetSideParam(direction, ksEndTypeEnum, depth, …)``SetSketch(эскиз)``Create()`.
Для выреза — `o3d_cutExtrusion`; для вращения — `o3d_bossRotated`/`o3d_cutRotated`.
4. Выбор грани для следующего эскиза:
`part.EntityCollection(o3d_face)``SelectByPoint(x, y, z)` → элемент коллекции как плоскость
эскиза.
5. Снимок результата: `set_view` (ориентация + fit) → `ksDocument3D.RasterFormatParam()`
`IRasterFormatParam.resultArrayBytes` → image-контент MCP. Агент анализирует кадр и выбирает
следующий шаг.
Используемые перечисления: `ksDirectionTypeEnum` (`dtNormal`/`dtReverse`/`dtBoth`/`dtMiddlePlane`),
`ksEndTypeEnum` (`etBlind`/`etThroughAll`/`etUpTo…`), `Obj3dType` (`o3d_sketch`, `o3d_bossExtrusion`,
`o3d_cutExtrusion`, `o3d_face`, `o3d_planeXOY`, …).
---
## 7. Обработка ошибок и безопасность
- **Трансляция ошибок COM** в структурированные MCP-ошибки: проверять HRESULT каждого вызова и
опрашивать `kompas.ksGetLastError()` / `IApplication.KompasError`; возвращать клиенту код + текст.
- **Null-чек после каждого приведения** COM-интерфейса (любой каст может вернуть `null`).
- **`.Init()` на param-структурах** перед использованием (`GetParamStruct(...)``.Init()`).
- **Гарантированный `EndEdit`** для эскизов через `try/finally` — незакрытый эскиз ломает документ.
- **Подтверждение деструктивных операций**: закрытие без сохранения, перезапись файла, `Quit`
только по явному флагу в аргументах инструмента.
- **Модель состояния**: инструменты по умолчанию работают с «активным документом»; опционально —
явный хэндл/идентификатор документа.
---
## 8. Сборка и запуск
```powershell
dotnet build -c Release -r win-x64 # сборка
# → kompas-mcp.exe (self-contained, win-x64)
```
Регистрация stdio-сервера у клиента (пример для конфигурации Claude Desktop):
```json
{
"mcpServers": {
"kompas3d": { "command": "C:\\path\\to\\kompas-mcp.exe", "args": [] }
}
}
```
**Ручная проверка (smoke):** запустить КОМПАС (или дать серверу запустить его) → через инструменты
построить простую деталь (выдавить окружность в цилиндр, затем `extrude_cut` — отверстие) →
убедиться визуально в окне КОМПАС → сохранить `.m3d`.
---
## 9. Стратегия тестирования
- **Интеграционные тесты** (требуют установленного КОМПАС): xUnit, категория `Integration`,
гейтятся (не идут в обычном CI без КОМПАС). Покрывают связку Core↔COM и центральный цикл.
- **Unit-тесты** чистой логики **без COM**: маппинг строк↔перечислений, валидация аргументов,
форматирование результатов/ошибок.
- **Smoke MCP**: проверка протокола через **MCP Inspector** или тестовый клиент (list tools, вызов
`kompas_connect`/`document_create`).
---
## 10. Дорожная карта
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection):** документы, эскизы, выдавливание/вырез, вращение, скругление/фаска, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`** (прямое редактирование грани, работает на импортированной B-rep); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели).
**Следующие приоритеты:**
1. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
2. Массивы (`ILinearPattern`, `ICircularPattern`).
3. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
4. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты.
5. Спецификации (`ISpecification`), построение сборок и сопряжения.
6. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
---
## 11. Открытые вопросы
- **Прямое редактирование B-rep, перемещение грани**: решено — `move_face` (`FaceEditService`) работает через `IPart7.FindObjectsByPoint` + `FaceMover`; проверено на импортированной B-rep. Рассечение/перемещение тела (`SplitSolids`/`BodyRepositions`) — механика есть, инструменты не реализованы (потенциальная следующая опция).
- **Дерево операций, тела, переменные, геометрические запросы**: решено — `ModelInspectionService` + `InspectionTools` (7 инструментов); юнит-тестируемый рендер через `InspectionText`.
- **Утечки транзитных RCW** в `PartModeler` (унаследованный паттерн): `EntityCollection`, `GetTopPart()` не освобождаются, накапливаются за длинную сессию.
- **Насос сообщений / CancellationToken**: `CancellationToken` не прерывает идущий COM-вызов; зависший модальный диалог КОМПАС блокирует всю очередь.
- **Embed Interop Types vs вендорские interop-сборки**: выбраны вендорские DLL из SDK `Samples/Common`; менять не планируется.