chore: initial skeleton + KOMPAS connection spike (S1/S2)

Зафиксирован фундамент проекта MCP-сервера КОМПАС-3D:

- docs/: ARCHITECTURE.md, IMPLEMENTATION_PLAN.md, presentation.html
- tools/kdoc.py: навигатор по справке SDK
- libs/kompas-interop/: вендорские interop-сборки КОМПАС из SDK
- src/Kompas.Mcp.ConnectSpike: net8.0-windows консоль, доказывает
  работу COM-интеропа и attach/launch подключения к КОМПАС
  (IApplication, версия 24.0)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 23:28:29 +03:00
commit 3a98afc67f
17 changed files with 1297 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
# Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант)
> Статус: **черновик / предлагаемый вариант**, открыт к правкам.
> Назначение: зафиксировать выбор стека и целевую архитектуру решения до начала
> реализации. Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md).
> Имена интерфейсов и перечислений сверены со справкой SDK через `tools/kdoc.py`.
---
## 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. Слои решения
В v1 — логические слои (папки/проекты одного solution `KompasMcp.sln`):
| Слой | Ответственность |
|---|---|
| **Kompas.Mcp.Host** | Точка входа. Сборка MCP-сервера (stdio), DI, логи в stderr, старт и владение STA-потоком, graceful shutdown. |
| **Kompas.Mcp.Tools** | Определения MCP-инструментов по категориям (System / Documents / Sketch / Features / Query). **Тонкий слой**: валидация аргументов → постановка задачи на STA-поток → форматирование результата/ошибки в MCP-ответ. |
| **Kompas.Mcp.Core** (`KompasSession`) | COM-слой. Менеджер соединения, менеджер документов, построитель эскизов, построитель операций, помощники выборки граней/плоскостей. Безопасные типизированные методы, null-проверки, трансляция ошибок. Здесь живут все паттерны работы с API5/API7. |
| **Kompas.Mcp.Interop** | Interop-сборки из `.tlb` + маппинг «дружелюбная строка ↔ перечисление» (`DocumentTypeEnum`, `Obj3dType`, `ksEndTypeEnum`, `ksDirectionTypeEnum`). |
Принцип: **только `Core` и `Interop` знают про COM**. `Tools` оперируют доменными
DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
---
## 5. Карта инструментов v1
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `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` — перестроить деталь.
**Selection / Query**
- `select_face_by_point` — выбрать грань точкой (для следующего эскиза).
- `list_bodies`, `list_faces_planes` — перечислить тела/грани и плоскости.
- `get_part_info` — МЦХ/габариты (`IMassInertiaParam7`).
**Vision — визуальная обратная связь («зрение агента»)**
- `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**,
чтобы мультимодальный агент *увидел* промежуточный результат и контролировал построение пошагово.
- `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)
1. Операции: `fillet` / `chamfer` / `shell` / `rib` / `loft` / `sweep`; массивы
(`ILinearPattern`, `ICircularPattern`).
2. Параметры и свойства: `IVariable7` / `IVariableTable`, `IPropertyMng` / `IPropertyKeeper`.
3. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …),
штриховки, тексты.
4. Спецификации (`ISpecification`), сборки и сопряжения (`IAssemblyDocument`).
5. Конвертеры/экспорт (STEP и др.) через `IApplication.Converter`.
6. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
---
## 11. Открытые вопросы
- **Один проект vs solution из 4 проектов** в v1 (склоняемся к одному solution, слои — папками,
выделять проекты по мере роста).
- **Embed Interop Types vs генерируемые interop-сборки** (`tlbimp`) — проверить на реальных
`.tlb` v24, выбрать по совместимости типов и размеру.
- **Стратегия именования инструментов** — подтвердить `snake_case` с группами-префиксами
(`document_*`, `sketch_*`, …).