# Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант)

> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-D start «переменные»+package-E «offset plane»+`hole`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`+`assembly_add_mate`+`drawing_create_standard_views`+`drawing_fill_title_block`+`drawing_add_linear_dimension`+`drawing_add_diametral_dimension`+`drawing_add_radial_dimension`+`drawing_add_angular_dimension`+`drawing_add_rough`+`drawing_add_text`+`drawing_set_technical_requirements` (веха «2D-чертёж», инкремент 6 — текстовые обозначения)). Актуализировано по коду.
> Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md).
> Имена интерфейсов и перечислений сверены со справкой SDK по MD-базе знаний `docs/Kompas3D_SDK/` (субагент `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, измерения), `AssemblyService` (вставка компонентов в сборку, namespace `Kompas.Mcp.Core.Assemblies`), `DrawingService` (стандартные виды чертежа, namespace `Kompas.Mcp.Core.Drawings`), снимок. Все паттерны API5/API7 живут здесь. |
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 267 тестов (163 unit + 104 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. |

Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.

Архитектурный принцип: **MCP транслирует возможности SDK КОМПАС в общие инструменты** (не под конкретную задачу). Методический слой поверх MCP — навык `.claude/skills/kompas-3d/` (playbook'и, эвристики). Полигон отработки подходов — `usecases/` (в .gitignore).

---

## 5. Карта инструментов (81 инструмент)

Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `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_create_on_offset_plane` — эскиз на плоскости, смещённой от базовой (XOY/XOZ/YOZ) на `offset` мм; вспомогательная геометрия через `o3d_planeOffset=14` / `ksPlaneOffsetDefinition`. Разблокирует `loft` (параллельные сечения на разной высоте). Начало пакета E «вспомогательная геометрия».
- `sketch_add_line` / `sketch_add_circle` / `sketch_add_arc` / `sketch_add_arc_3points` / `sketch_add_rectangle`.
- `sketch_add_ellipse` (centre, semi-axes A/B, rotation angle) / `sketch_add_polyline` (point chain, closed) / `sketch_add_polygon` (regular, inscribed/circumscribed) / `sketch_add_spline` (cubic NURBS, closed) / `sketch_add_point`.
- `sketch_close` — завершить редактирование.
- Жизненный цикл: `NewEntity(o3d_sketch)` → `SetPlane(plane|face)` → `BeginEdit()` →
  `ksLineSeg` / `ksCircle` / `ksArcBy3Points` / `ksArcByAngle` / `ksEllipse` / `ksRegularPolygon` / `ksNurbs` / … → `EndEdit()`.
- `PartModeler` split into partial classes: `PartModeler.cs` (core), `PartModeler.Sketch.cs`, `PartModeler.Features.cs`. Static `SketchGeometry` class holds mappings and validators. Point lists use `record SketchPoint(X, Y)` (JSON `x`/`y`). All angles in degrees.
- `CoordinateAxis.cs` (`src/Kompas.Mcp.Core/Modeling/`) — enum `CoordinateAxis {X,Y,Z}` + `CoordinateAxes.ToObj3dType/Parse` (→ `o3d_axisOX/OY/OZ = 71/72/73`), analogous to `BasePlane.cs`.

**Features** (3D-операции)
- `extrude_boss` / `extrude_cut` — sketch, depth, direction, endType.
- `revolve_boss` / `revolve_cut` — угол.
- `shell` — превратить тело в оболочку: открыть грани по индексам (`faceIndices`, ≥1, из `list_faces`), задать толщину стенки (`thickness` мм), направление (`outward`, по умолчанию внутрь). Реализовано через API5 `ksPart.NewEntity(o3d_shellOperation=43)` → `ksShellDefinition` → `FaceArray()` → `Create()`. Маппинг: `thinType = !outward`.
- `rib` — построить ребро жёсткости от разомкнутого контура эскиза до тела. Параметры: `sketchId`, `thickness` (мм), `side` (`left|right|up|down` → 0/1/2/3), `symmetric` (толщина симметрична плоскости), `angle` (уклон стенок, °). Реализовано через `ksPart.NewEntity(o3d_ribOperation=44)` → `ksRibDefinition` (`SetSketch`, `SetThinParam`). `symmetric=true`: `SetThinParam(dtBoth, t/2, t/2)`; иначе `SetThinParam(dtNormal, t, 0)`. Контур должен не касаться тела концами — КОМПАС дотягивает полотно сам.
- `sweep` — кинематическая операция: перемещение замкнутого профиля (эскиз `profileSketchId`) вдоль траектории (эскиз `pathSketchId`), образуя сплошное тело. Профиль и траектория на разных (обычно перпендикулярных) плоскостях. Реализовано через `ksPart.NewEntity(o3d_baseEvolution=45)` → `ksBaseEvolutionDefinition` (`SetSketch(профиль)` → `PathPartArray().Add(траектория)` → `sketchShiftType=0` → `SetThinParam(false)`) → `Create()`. Валидация: `profileSketchId != pathSketchId`.
- `loft` — операция по сечениям: построить сплошное тело по ≥2 закрытым эскизам-сечениям на параллельных плоскостях. Реализовано через `ksPart.NewEntity(o3d_baseLoft=30)` → `ksBaseLoftDefinition` (`Sketchs().Add(каждое сечение)` → `SetLoftParam(closed=false,false,true)` → `SetThinParam(false)`) → `Create()`. Для сечений не на базовых плоскостях использовать `sketch_create_on_offset_plane`.
- `linear_pattern` — линейный массив операций вдоль координатной оси (`o3d_meshCopy=35`, `ksMeshCopyDefinition`); параметры: `featureIds[]`, `axis` (X/Y/Z), `count` (включает исходный, ≥2), `step` (мм). `SetAxis1` + `SetCopyParamAlongAxis(true,0,count,step,false)` + `count2=1` (выключает 2-е направление).
- `circular_pattern` — круговой массив вокруг координатной оси (`o3d_circularCopy=36`, `ksCircularCopyDefinition`); параметры: `featureIds[]`, `axis`, `count`, `step` (угол° между соседними), `reverse`, `geometric`. Свойства `count1=1`/`count2=count`/`step2=angle°`/`factor2=false`/`inverce=reverse` задаются напрямую; `GetOperationArray()`.
- `mirror_operation` — зеркальная копия операций (`o3d_mirrorOperation=48`, `ksMirrorCopyDefinition`); параметры: `featureIds[]`, `plane` (XOY/XOZ/YOZ). `SetPlane` + `GetOperationArray()`.
- `mirror_body` — зеркало всего тела (`o3d_mirrorAllOperation=49`, `ksMirrorCopyAllDefinition`); параметр: `plane`. `ChooseBodies()` не приводится к `ksChooseBodies` и не нужен — операция отражает все тела, сохраняя оригинал (объём удваивается).
- `hole` — цилиндрическое отверстие (сквозное или глухое) на грани, найденной по мировой точке (x,y,z). Параметры: `diameter` (мм), `depth` (мм, для глухого), `throughAll`. Реализовано через **API7** (`HoleService`, `src/Kompas.Mcp.Core/Modeling/HoleService.cs`) — в API5 интерфейс `ksHoleDefinition` отсутствует в interop. Паттерн: `(IModelContainer)part7` → `Points3D.Add()` (центр, `IPoint3D`) → `Holes3D.Add()` → `IHole3D` (`HoleType=ksHTBase`, `Diameter`, `DepthType`, `Depth`, `EndFaceType=ksEFFlat`) → `(IHoleDisposal)hole` (`BaseSurface=грань` через `FindObjectsByPoint`, `Perpendicular=true`, `AssociationVertex=point`) → `Update()` → `RebuildDocument()`. Направление сверления определяется **по факту убыли объёма**: `Direction=true` → Rebuild → сравнить объём (API5 `CalcMassInertiaProperties`); если не убыл → `Direction=false`. При неуспехе — откат через `IFeature7.Delete()`. Возвращает подтверждение без id (отверстие не попадает в реестр `_features` API5).
- `hole_counterbore` — отверстие с цековкой (цилиндрическое уширение сверху, под винт с цилиндрической головкой). Параметры: `x,y,z`, `diameter`, `spotfaceDiameter` (>diameter), `spotfaceDepth`, `depth=0`, `throughAll=false`. `HoleType=ksHTCounterbore`; `HoleParameters` приводится к `ISpotfacingHoleParameters` (`SpotfacingDiameter`, `SpotfacingDepth`) — настройка через `configure`-колбэк после фиксации типа. Для глухого: валидируется `depth>spotDepth`.
- `hole_countersink` — отверстие с зенковкой (коническое уширение сверху, под винт с потайной головкой). Параметры: `x,y,z`, `diameter`, `sinkDiameter` (>diameter), `sinkAngle` (0<angle<180, типично 90°), `depth=0`, `throughAll=false`. `HoleType=ksHTCountersinking`; `HoleParameters` → `ICountersinkHoleParameters` (`CountersinkType=ksCTDiameterAngle`, `CountersinkDiameter`, `CountersinkAngle`). Все три инструмента отверстий используют общую логику размещения и подбора направления (`HoleCore`).
- `hole_conic` — коническое (конусное) отверстие, сужающееся вглубь. Параметры: `x,y,z`, `diameter` (диаметр у основания/поверхности), `conicAngle` (угол конуса, 0<angle<180°), `depth=0`, `throughAll=false`. `HoleType=ksHTConic`; `HoleParameters` → `IConicHoleParameters` (`ConicType=ksCNAngle`, `ConicAngle`). Завершает набор типов отверстий (простое, цековка, зенковка, коническое).
- `draft` — уклон граней на угол `angle` (°, строго 0–90) относительно нейтральной координатной плоскости `neutralPlane` (XOY/XOZ/YOZ). `outward=true` — расширение (добавляет материал), `false` — сужение. Реализовано через **API5** `ksInclineDefinition` (`o3d_incline=42`) — операция называется Incline в API5, а не Draft. Паттерн: `ksPart.NewEntity(o3d_incline)` → `GetDefinition() as ksInclineDefinition` → `FaceArray()` (грани по индексам из `list_faces`) → `SetPlane(базовая координатная плоскость)` → `angle` → `direction=!outward` → `Create()` → регистрация в `_features`. Эмпирика: `direction=false`=расширение, `direction=true`=сужение — **обратно справке** → маппинг `def.direction = !outward`. Валидация: `RequireDraftAngle(0<angle<90)`. Регистрируется в `_features` → применимы массивы и зеркало (пакет C).
- `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 мм.
- `split_solid_by_plane` — рассечь тело плоскостью (по грани или базовой плоскости).
- `move_body` — переместить тело на заданный вектор (dx/dy/dz, мм).
- `boolean_union` — булева операция объединения тел.

**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`; `ksPart.GetFeature().VariableCollection` → `ksVariable` (все переменные; `ksPart.VariableCollection()` — только внешние); `GetMeasurer()` → `ksMeasurer`.
«Импорт STEP без истории» определяется структурно: `bodyCount > 0 && нет формообразующих операций && features.Count <= bodyCount+1`.

**Variables — управление переменными модели (package D start)**
- `create_variable` — создать переменную (имя, начальное значение, примечание, флаг внешней).
- `set_variable` — задать выражение (константа `"30"` или формула `"width*2+5"`); десятичный разделитель — точка; модель перестраивается, зависимые переменные пересчитываются.
- `delete_variable` — удалить переменную (нельзя при наличии зависимых).

Реализовано через `VariableService` (`src/Kompas.Mcp.Core/Modeling/VariableService.cs`, API5), инструменты — `VariableTools` (`src/Kompas.Mcp.Host/Tools/VariableTools.cs`). DI: `AddSingleton<VariableService>()` в `Program.cs`.

Ключевые API-паттерны: создание — ТОЛЬКО через `ksPart.GetFeature().VariableCollection` (свойство корневого `ksFeature`), а НЕ `ksPart.VariableCollection()` (та возвращает только внешние); Expression — ведущее поле, Value — вычисленный результат; изменение через Expression + `ksPart.RebuildModel()`; после rebuild RCW переменной стал; значение перечитывается через свежую коллекцию (`ReadValueFresh`).

**Ограничение:** переменная управляет геометрией только в параметрической модели, где размеры эскизов привязаны к именам переменных. Наши эскизы строятся литеральными координатами — `set_variable` хранит и вычисляет значение, но геометрию не меняет. Связь размеров эскизов с переменными (параметрические эскизы через API2D) — не реализована (package D продолжение).

**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`, …) — будущие инкременты.

**Drawing — 2D-чертёж (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`)**
- `drawing_create_standard_views(partFilePath, scale, x, y)` — создать стандартные ассоциативные виды (спереди/сверху/слева, `ksPtFront/ksPtUp/ksPtLeft`) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже. Реализовано через API7 `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, dx=20, dy=20)` — `object[]` передаётся как SAFEARRAY VT_I4. Проверки: `created==3` строго; непустота — сумма `IView.ObjectCount` новых видов (по разнице `IView.Number`) `>0`, иначе ошибка + откат (`IView.Delete`). Возвращает `DrawingViewsResult{Created,Total,ViewNumbers}` — `ViewNumbers` содержит номера созданных видов (для адресации при простановке размеров). Модель должна быть сохранена до вызова.
- `drawing_fill_title_block(designation?, name?, material?)` — заполнить графы основной надписи (штампа) активного чертежа. `designation` = обозначение документа (графа 2), `name` = наименование изделия (графа 1), `material` = материал (графа 3); нужно хотя бы одно поле. Реализовано: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (индексированное свойство; `IText.Str` замещает содержимое без `Clear`) → `stamp.Update()`. Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`. Возвращает число заполненных граф.
- `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — поставить линейный размер на виде активного чертежа. Координаты (x1,y1)-(x2,y2) — выносные точки в **локальной СК вида** (мм); (x3,y3) — положение размерной линии; `orientation`: `horizontal`|`vertical`|`parallel`; `viewNumber` — номер вида (0=первый). Значение измеряется автоматически (`AutoNominalValue=true`). Реализовано: `(ISymbols2DContainer)view` (COM-QI от конкретного `IView`, не от листа — размер попадает именно в этот вид) → `LineDimensions.Add()` → `ILineDimension` → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Ключевая находка: **размеры не входят в `IView.ObjectCount`** (он считает геометрию `IDrawingContainer`); проверять через `ISymbols2DContainer.LineDimensions.Count`. Откат через `dim.Delete()` при ошибке или нулевом значении. Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`.
- `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber)` — поставить диаметральный размер (Ø) окружности на виде активного чертежа. `xc,yc` — центр, `radius` — радиус (мм, локальная СК вида); `angle` — направление выноски (градусы → радианы через `DimensionAngles.ToRadians`); `viewNumber` — номер вида (0=первый). Значение (диаметр = 2·radius) измеряется автоматически. Реализовано: общий хелпер `RequireSymbols2DContainer(viewNumber)` (переиспользуется с линейным размером) → `DiametralDimensions.Add()` → `IDiametralDimension` (`Xc`, `Yc`, `Radius`, `Angle` в радианах, `AutoNominalValue=true`) → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Размер «свободный» (без `BaseObject`). Валидатор `DrawingValidation.RequirePositiveRadius`. `DimensionAngles.ToRadians` добавлен в `DimensionOrientation.cs`.
- `drawing_add_radial_dimension(xc,yc,radius, angle, viewNumber)` — поставить радиальный размер (R) окружности/дуги на виде активного чертежа. `xc,yc` — центр, `radius` — радиус (мм, локальная СК вида); `angle` — направление выноски (°, по умолчанию 0); `viewNumber` — номер вида. Возвращаемое значение равно **радиусу** (не диаметру). Реализовано: `RequireSymbols2DContainer(viewNumber)` → `RadialDimensions.Add()` → `IRadialDimension` (`Xc`, `Yc`, `Radius`, `Angle` в радианах, `DimensionType=true`, `AutoNominalValue=true`) → `Update()` → `Valid` → `NominalValue`. Размер «свободный» (без `BaseObject`). Переиспользует `RequirePositiveRadius` и `DimensionAngles.ToRadians`.
- `drawing_add_angular_dimension(xc,yc, x1,y1, x2,y2, x3,y3, angleType, viewNumber)` — поставить угловой размер между двумя сторонами на виде активного чертежа. `xc,yc` — вершина угла; `x1,y1` — точка на стороне 1; `x2,y2` — точка на стороне 2; `x3,y3` — положение размерной дуги (задаёт её радиус); `angleType` = `"min"` (острый) | `"max"` (тупой, 180°-min) | `"more"` (рефлексный >180°); `viewNumber` — номер вида. Значение в градусах измеряется автоматически. Реализовано: `RequireSymbols2DContainer(viewNumber)` → `AngleDimensions.Add(ksDrADimension)` → `IAngleDimension` (`Xc`, `Yc`, `X1`, `Y1`, `X2`, `Y2`, `X3`, `Y3`, `DimensionType=ksAngleDimTypeEnum`, `AutoNominalValue=true`) → `Update()` → `Valid` → `NominalValue`. Новый enum `AngleDimensionType {Min, Max, More}` + `AngleDimensionTypes.Parse/ToKompas` в `DimensionOrientation.cs`; 9 unit-тестов для маппинга. Размер «свободный» (без `BaseObject`). Валидаторы `RequireFiniteCoords` + `RequireDistinctPoints`.
- `drawing_add_rough(x,y, value?, signType, angle, viewNumber)` — поставить знак шероховатости на виде. `x,y` — положение знака в локальной СК вида (мм); `value` — текст параметра (напр. `"Ra 1.6"`; пусто = знак без значения); `signType`: `"delete"` (с удалением слоя материала) | `"without"` (без удаления) | `"none"` (без указания); `angle` — угол наклона оси знака (°, по умолч. 0); `viewNumber` — номер вида. Реализовано: `ISymbols2DContainer.Roughs.Add()` → `IRough` (`BranchX0/Y0/Angle`) → `(IRoughParams)rough` (`SignType=ksRoughSignEnum`, `RoughParamText.Str = value`) → `Update()` → `Valid`. Новый enum `RoughSignType {NoProcessing, DeleteMaterial, WithoutDeleteMaterial}` + `RoughSignTypes.Parse/ToKompas` в `Drawings/RoughSignType.cs`. Возвращает `DrawingAnnotationResult { Value, ViewNumber }`.
- `drawing_add_text(x,y, text, angle, viewNumber)` — свободная текстовая надпись на виде стилем по умолчанию. `x,y` — точка привязки (СК вида, мм); `text` — содержимое (`\n` — многострочно); `angle` — угол (°, по умолч. 0); `viewNumber` — номер вида. Реализовано: `(IDrawingContainer)view.DrawingTexts.Add()` → `IDrawingText` (`X/Y/Angle`) → `(IText)dt.Str = text` → `Update()` → `Valid`. ВАЖНО: текст живёт в `IDrawingContainer` (контейнер геометрии вида), НЕ в `ISymbols2DContainer`; попадание проверяется через `DrawingTexts.Count`. Возвращает `DrawingAnnotationResult { Value, ViewNumber }`.
- `drawing_set_technical_requirements(text)` — технические требования чертежа (единый текстовый блок, уровень документа, не вида). `text` — строки через `\n`. Реализовано: `(IDrawingDocument)doc.TechnicalDemand` → `td.Text.Str = text` → `td.Update()`. Перезаписывает прежние (не добавляет). Возвращает число строк.

**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+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension+drawing_add_diametral_dimension+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели); **`create_variable`, `set_variable`, `delete_variable`** (управление переменными, package D start); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`); **`assembly_add_mate`** (сопряжения `coincidence`/`distance`, API7, `IMateConstraints3D`); **`drawing_create_standard_views`** (стандартные ассоциативные виды чертежа, API7, `DrawingService`); **`drawing_fill_title_block`** (заполнение основной надписи/штампа: обозначение, наименование, материал; `IStamp.Text[id].Str`, API7, `DrawingService`); **`drawing_add_linear_dimension`** (линейный размер на виде чертежа: `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`, координаты в локальной СК вида, API7, `DrawingService`); **`drawing_add_diametral_dimension`** (диаметральный размер Ø: `ISymbols2DContainer.DiametralDimensions.Add`/`IDiametralDimension`, Xc/Yc/Radius/Angle в радианах, API7, `DrawingService`); **`drawing_add_radial_dimension`** (радиальный размер R: `ISymbols2DContainer.RadialDimensions.Add`/`IRadialDimension`, значение = радиус, API7, `DrawingService`); **`drawing_add_angular_dimension`** (угловой размер: `ISymbols2DContainer.AngleDimensions.Add`/`IAngleDimension`, `angleType`=min/max/more, значение в градусах, API7, `DrawingService`); **`drawing_add_rough`** (знак шероховатости: `ISymbols2DContainer.Roughs.Add`/`IRough`/`IRoughParams`, `signType`=delete/without/none, API7, `DrawingService`); **`drawing_add_text`** (свободная текстовая надпись на виде: `IDrawingContainer.DrawingTexts.Add`/`IDrawingText`/`IText`, API7, `DrawingService`); **`drawing_set_technical_requirements`** (технические требования чертежа: `IDrawingDocument.TechnicalDemand`/`ITechnicalDemand`/`IText`, уровень документа, API7, `DrawingService`). Класс «2D-чертёж» (инкремент 6): стандартные виды, основная надпись, размеры (линейный, диаметральный, радиальный, угловой), текстовые обозначения (шероховатость, свободный текст, технические требования).

**Следующие приоритеты:**
1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D). ⚠️ **Исследовано**: `ksCDimWithVariable` недоступен из внешней автоматизации — см. `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`.
2. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу.
3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
5. **2D-чертёж (продолжение):** инкремент 6 добавил текстовые обозначения — `drawing_add_rough` (шероховатость), `drawing_add_text` (свободный текст), `drawing_set_technical_requirements` (тех. требования). Нереализовано: ассоциативная привязка размеров и шероховатости к геометрии (`BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`).
6. Сборки — продолжение: дополнительные типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`; `coincidence`/`distance` реализованы); авто-позиционирование компонентов; спецификации (`ISpecification`).
7. Транспорт **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`; менять не планируется.
