commit 3a98afc67f0c4ace84994537b5245b0763bb3b76 Author: Shahovalov MIkhail Date: Mon May 25 23:28:29 2026 +0300 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f800b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# ============================================================================ +# .gitignore — KompasMcp (MCP-сервер КОМПАС-3D, .NET/C#) +# ============================================================================ + +# ---- .NET / MSBuild build output ------------------------------------------- +[Bb]in/ +[Oo]bj/ +artifacts/ +*.binlog +project.lock.json +project.assets.json +*.nuget.props +*.nuget.targets + +# ---- Visual Studio / Rider / VS Code local state --------------------------- +.vs/ +.vscode/ +.idea/ +*.user +*.suo +*.userosscache +*.sln.docstates +*.DotSettings.user + +# ---- Test / coverage ------------------------------------------------------- +[Tt]est[Rr]esult*/ +[Cc]overage*/ +*.coverage +*.trx + +# ---- NuGet ----------------------------------------------------------------- +*.nupkg +*.snupkg +**/packages/* +!**/packages/build/ + +# ---- Claude Code local settings (не разделяемые) --------------------------- +.claude/settings.local.json + +# ---- Python tooling (tools/) ----------------------------------------------- +__pycache__/ +*.py[cod] + +# ---- OS-мусор -------------------------------------------------------------- +Thumbs.db +ehthumbs.db +Desktop.ini +.DS_Store + +# ============================================================================ +# Большие вендорские/генерируемые артефакты — НЕ коммитим +# ============================================================================ + +# Офлайн-зеркало справки SDK КОМПАС-3D (~1.4 ГБ, 26k+ файлов, © АСКОН). +# Лежит локально; для навигации см. tools/kdoc.py. Скачивается отдельно. +docs/KOMPAS_SDK_ru-RU/ + +# Индекс справки, генерируется из зеркала командой: python tools/kdoc.py build-index +docs/kompas_sdk_index.tsv + +# ============================================================================ +# Отслеживается НАМЕРЕННО (нужно для сборки), не игнорировать: +# libs/kompas-interop/*.dll — interop-сборки КОМПАС из SDK (Samples/Common) +# ============================================================================ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..19a153d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project goal + +Build an **MCP (Model Context Protocol) server** that lets an LLM drive the CAD system **КОМПАС-3D** (ASCON). The server exposes КОМПАС operations (create/open documents, build 2D geometry, 3D parts and assemblies, read/write parameters, run macros) as MCP tools. + +Authoritative references: +- **Offline SDK docs mirror in this repo: `docs/KOMPAS_SDK_ru-RU/`** — navigate it with `tools/kdoc.py` (see below). Prefer this over the web. +- Online SDK docs: https://help.ascon.ru/KOMPAS_SDK/22/ru-RU/index.html +- Local SDK install: `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK` + +## Navigating the SDK docs (`tools/kdoc.py`) + +`docs/KOMPAS_SDK_ru-RU/` is a 26 000-page Help&Manual WebHelp export — raw HTML buried under header/footer/JS noise, with cryptic filenames. **Do not browse it by hand.** Use the helper: + +``` +python tools/kdoc.py search "" # find pages: prints "file.html\tTitle" +python tools/kdoc.py read # print clean topic text (no markup) +python tools/kdoc.py build-index # regenerate the index (only if docs change) +``` + +`search` does AND-matching over titles + excerpts; `read` extracts just the topic body (breadcrumb path, data type, Automation/COM syntax, parameters, return value, notes). Example: `search Visible IApplication` → `iapplication_visible.html`, then `read iapplication_visible.html`. + +The index lives at `docs/kompas_sdk_index.tsv` (`filenametitleexcerpt`) and can also be searched with the Grep tool directly — handy for fanning out over many interface names at once. + +**The docs contain two parallel mirrors of the same interfaces:** +- *«API интерфейсов. Версия 7»* — filenames **without** prefix (`iapplication_*`, `idocuments_*`). This is the **COM API7/API5** reference — the one this project uses. +- *«API интерфейсов. KsAPI»* — `ksapi_*`-prefixed filenames: the cross-platform Qt/C++ **KsAPI** flavour. Same concepts, different (non-COM) binding — ignore unless explicitly working with KsAPI. + +## Current state + +The repository is **empty / greenfield** — no source, no git, no build system yet. The implementation language and MCP transport have **not** been chosen. Before writing code, decide and record (here) the stack. The natural fit is **.NET / C#** because the КОМПАС API ships as COM type libraries with first-class C# Interop samples (see SDK `Samples/CSharp.zip`), but a Python MCP server using `pywin32`/`comtypes` is also viable. + +## КОМПАС-3D API architecture (the critical context) + +КОМПАС-3D is automated via a **COM Automation API**, Windows-only. КОМПАС-3D **must be installed and running/launchable** on the same machine — the MCP server is a COM client, not a standalone CAD engine. There are **two coexisting API generations**: + +| | Entry point | Namespace (C#) | Style | Use when | +|---|---|---|---|---| +| **API5** (legacy) | `KompasObject` | `Kompas6API5` | procedural, flat | older features, 2D primitives, bootstrapping | +| **API7** (modern, preferred) | `IApplication` / `_Application` | `KompasAPI7` | OOP, interface-based | everything new — documents, 3D, parameters | + +Constants live in separate libraries: `Kompas6Constants`, `Kompas6Constants3D` (C#), or `ksConstants.h` / `ksConstants3D.h` (C++). + +### Connection pattern (verified from `Samples/CSharp.zip`) + +1. Get the КОМПАС root object by COM ProgID. **Verified on this machine (v24 Home):** + `KOMPAS.Application.7` and `KOMPAS.Application.5` are registered; `KOMPASLT.*` is **absent** — + the Home edition uses the regular (non-LT) ProgIDs. Resolution order: + - `KOMPAS.Application.7` — direct API7 `IApplication` (preferred) + - `KOMPAS.Application.5` — API5 `KompasObject`, then `ksGetApplication7()` + - `KOMPASLT.Application.5` — fallback for other installations + - New instance: `Activator.CreateInstance(Type.GetTypeFromProgID(progId))` + - Attach to a running instance: `Marshal.GetActiveObject(progId)` +2. If you entered via API5, obtain the modern application: `IApplication appl = (IApplication)kompas.ksGetApplication7();` (via `KOMPAS.Application.7` you already hold `IApplication`). +3. Set `appl.Visible = true` to show the КОМПАС window; drive documents via API7 interfaces (`IKompasDocument2D`, `IKompasDocument3D`, …). + +> The SDK C# samples are mostly **КОМПАС plugin libraries** (DLLs loaded *into* КОМПАС as ActiveX, entry methods like `ExternalRunCommand`/`ExternalMenuItem`, registered via `[ComRegisterFunction]`). For an MCP server we want the **opposite direction**: a standalone *external automation client* that connects out-of-process via the ProgID pattern above. Read the samples for API usage, not for the plugin packaging/registration boilerplate. + +## SDK layout (read-only reference, not part of this repo) + +Under `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK`: +- `lib\*.tlb` — COM type libraries to reference / generate interop from: `kAPI5.tlb`, `kAPI7.tlb`, `ksConstants.tlb`, `ksConstants3D.tlb`, `kAPI2D5COM.tlb`, `kAPI3D5COM.tlb`. +- `lib64\` — 64-bit import libs (`kAPI7.lib`, `kAPI5.lib`, …) and `.a` for C++/Builder. +- `Include\` — C++/Pascal headers (`Ks_TLB.h`, `kAPI2D5COM.h`, `LDefin2D.pas`, …). +- `KsAPI\` — the **KsAPI** (Qt/C++ cross-platform flavour): `Include\KsAPI.h`, `ksConstants*.h`, `Lib64\ksAPI.lib`, plus `Help\С чего начать.pdf` and a Linux/Debian 12 build guide. +- `Samples\` — zipped examples per language: `CSharp.zip`, `C++.zip`, `Pascal.zip`, `Basic.zip`. Inside `CSharp.zip`, the `Step1`…`Step12` and `Step2_API7_2D` / `Step2_API7_3D` projects are the progressive tutorial; `Gayka`, `SlideWrk`, `EventsAuto` are larger feature demos. +- `Help\KOMPAS_SDK_ru-RU.zip` and `KsAPI\Help\KsAPI_Help.zip` — offline copy of the docs. + +To inspect a sample without unpacking the whole archive: `unzip -p "\Samples\CSharp.zip" "Automation/Step2_API7_3D/Step2_API7_3D.cs"`. Note the `.cs` sample sources are **Windows-1251 encoded** (Cyrillic comments appear garbled in UTF-8 tools). + +## Conventions / gotchas + +- **Windows-only & 64-bit**: target x64 to match the installed КОМПАС; mixing bitness breaks COM activation. КОМПАС v24 Home is the installed edition — some full-edition API features may be unavailable. +- **Encoding**: SDK source samples are CP1251. New project files should be UTF-8. +- **COM lifetime**: release COM objects (`Marshal.ReleaseComObject`) / scope them; a leaked reference keeps the КОМПАС process alive. +- **API choice**: prefer API7 for new tool implementations; drop to API5 only for things API7 doesn't expose. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..05fc0b8 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,15 @@ + + + + net8.0-windows + x64 + + x64 + enable + enable + latest + false + + $(MSBuildThisFileDirectory)libs\kompas-interop + + diff --git a/KompasMcp.slnx b/KompasMcp.slnx new file mode 100644 index 0000000..df3975e --- /dev/null +++ b/KompasMcp.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..7cf5dd5 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -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>` или собственный + `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_*`, …). diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..1bbc293 --- /dev/null +++ b/docs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,115 @@ +# План реализации MCP-сервера КОМПАС-3D + +> Документ описывает, **как** строить сервер. Что строим и почему — в +> [`ARCHITECTURE.md`](ARCHITECTURE.md). Прогресс отражается в +> [`presentation.html`](presentation.html) (TODO). + +## Принцип + +**«Ходячий скелет» + спайки на риски.** Сначала сквозной тонкий срез +(подключение → снимок), который проходит через все слои и доказывает, что связка +COM ↔ STA ↔ MCP работает. Затем наращиваем инструменты, и **каждый шаг построения сразу +проверяем визуально** (`model_snapshot`) — это и есть целевой UX «строю под контролем». + +## Окружение (проверено 2026-05-25) + +| | Факт | Следствие для плана | +|---|---|---| +| .NET SDK | **10.0.108** (отдельного .NET 8 SDK нет; рантайм 8 есть) | таргет **`net8.0-windows`** (LTS), собирается SDK 10 | +| ProgID КОМПАС | **`KOMPAS.Application.7`** и **`KOMPAS.Application.5`** зарегистрированы; `KOMPASLT.*` **отсутствует** | подключаться напрямую к API7 через `KOMPAS.Application.7`; LT — только запасной | +| Процесс | КОМПАС не запущен | спайк подключения должен уметь **launch**, не только attach | +| SDK | `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK` (`lib\*.tlb`) | источник interop | + +**Прерэквизит разработчика:** установленный КОМПАС v24 + возможность его запускать. + +--- + +## Спайки (делать первыми — снимают главные неизвестные) + +| # | Риск / вопрос | Критерий успеха | +|---|---|---| +| **S1** | Генерация COM-interop из `.tlb` в SDK-проекте | `net8.0-windows` проект с `` на `kAPI7.tlb`, `kAPI5.tlb`, `ksConstants.tlb`, `ksConstants3D.tlb` собирается; типы `IApplication`, `KompasObject` доступны. Запасной путь — `tlbimp`/ручные interop-сборки | +| **S2** | Подключение по ProgID | консольная проба: attach (`Marshal.GetActiveObject`) → при отказе launch (`Activator.CreateInstance(Type.GetTypeFromProgID("KOMPAS.Application.7"))`); получить `IApplication`, `Visible=true`, прочитать версию | +| **S3** | STA-поток + насос сообщений + async MCP | выделенный STA-поток выполняет COM-вызовы из пула задач без `RPC_E_WRONGTHREAD`; насос сообщений не вешает процесс | +| **S4** | Рендер модели в растр на v24 Home | `ksDocument3D.RasterFormatParam()` → `resultArrayBytes` отдаёт корректные PNG-байты открытого 3D-документа | + +Спайки — выбрасываемый код в `spikes/`; выводы переносятся в `Core`. + +--- + +## Фазы + +### Фаза 0 — Каркас репозитория +- `git init`, `.gitignore` (.NET + `bin/`, `obj/`; **не** игнорировать `docs/`). +- `KompasMcp.sln`; проекты `Kompas.Mcp.Host`, `Kompas.Mcp.Core`, `Kompas.Mcp.Interop`, `Kompas.Mcp.Tools` + (в v1 допустимо начать одним проектом, разнося по папкам). +- `Directory.Build.props`: `TargetFramework=net8.0-windows`, `Platforms=x64`, `Nullable=enable`, `LangVersion=latest`. +- Пакеты: `ModelContextProtocol`, `Microsoft.Extensions.Hosting`. +- **Готово:** `dotnet build -c Release` проходит; пустой stdio-хост стартует. + +### Фаза 1 — Interop + маппинг констант +- `` на типобиблиотеки (итог S1). Зафиксировать стратегию (embed vs сборки). +- `Kompas.Mcp.Interop`: хелперы «строка ↔ enum» — `DocumentTypeEnum` (part=4, assembly=5, drawing=1, fragment=2), + `Obj3dType`, `ksEndTypeEnum`, `ksDirectionTypeEnum`, базовые плоскости (`o3d_planeXOY/XOZ/YOZ`). +- **Готово:** unit-тесты маппинга (без COM) зелёные. + +### Фаза 2 — STA-диспетчер +- `KompasThread`: выделенный STA-поток, очередь задач (`Channel`/`BlockingCollection`), + `Task InvokeAsync(Func)`, насос сообщений + периодический `PumpWaitingMessages` (итог S3). +- **Готово:** интеграционный тест — параллельные `InvokeAsync` исполняются строго на одном STA-потоке. + +### Фаза 3 — Connection + вертикальный срез (MCP) +- `KompasSession`: resolve ProgID (`KOMPAS.Application.7` → `.5`+`ksGetApplication7()` → `KOMPASLT.*`), + attach/launch, `Visible`, версия/редакция, освобождение COM, `Quit` только если запускали мы. +- Хост: stdio-сервер, **логи в stderr**, регистрация инструментов через DI. +- Инструменты `System`: `kompas_connect`, `kompas_status`, `kompas_set_visible`. +- **Готово (веха «скелет»):** из MCP Inspector вызвать `kompas_connect` → КОМПАС стартует и виден; `kompas_status` отвечает. + +### Фаза 4 — Documents +- `document_create` (part/assembly/drawing/fragment), `document_open`, `document_save`, `document_close`, `document_active`. +- **Готово:** создать деталь, сохранить `.m3d`, закрыть, открыть заново. + +### Фаза 5 — Vision (раннее — делает всё дальнейшее проверяемым) +- `set_view` (ориентация iso/front/top/right, zoom-to-fit, `ShadedWireframe`), `model_snapshot` + (`RasterFormatParam.resultArrayBytes` → image-контент MCP) — итог S4. +- **Готово:** `model_snapshot` возвращает PNG открытой детали; агент видит изображение в ответе инструмента. + +### Фаза 6 — Sketch +- `sketch_create` (плоскость или грань), `sketch_add_line|circle|arc|rectangle`, `sketch_close` + (`NewEntity(o3d_sketch)`→`SetPlane`→`BeginEdit`/примитивы/`EndEdit`, гарантированный `EndEdit` в `finally`). +- **Готово:** построить эскиз-окружность на XOY и увидеть его на снимке. + +### Фаза 7 — Features +- `extrude_boss`/`extrude_cut` (depth, direction, endType), `revolve_boss`/`revolve_cut`, `rebuild`. +- **Готово (целевой демо-сценарий):** окружность → `extrude_boss` (цилиндр) → `model_snapshot` → + выбрать грань → эскиз → `extrude_cut` (отверстие) → `model_snapshot`. Полный цикл со зрением. + +### Фаза 8 — Query / Selection +- `select_face_by_point`, `list_bodies`, `list_faces_planes`, `get_part_info` (МЦХ/габариты). +- **Готово:** агент может выбрать грань для следующего эскиза по координатам. + +### Фаза 9 — Качество и поставка +- Unit-тесты (маппинг, валидация, форматирование); интеграционные (`[Trait("Category","Integration")]`, гейтятся). +- Трансляция ошибок COM (`ksGetLastError`/`IApplication.KompasError`) в структурированные MCP-ошибки. +- Сборка `kompas-mcp.exe` (`dotnet publish -c Release -r win-x64`), регистрация stdio-сервера в конфиге клиента. +- Escape-hatch `run_kompas_command` (опц.). + +--- + +## Порядок и зависимости + +``` +S1,S2 ─► Фаза 0 ─► Фаза 1 ─► Фаза 2 ─► Фаза 3 (скелет) ─► Фаза 4 ─► Фаза 5 (зрение) + │ + Фаза 6 ─► Фаза 7 ─► Фаза 8 ─► Фаза 9 +S3 ─► Фаза 2 S4 ─► Фаза 5 +``` + +Критический риск — **S1/S3** (interop + STA). Если `` не заработает на v24 `.tlb` — +переход на сгенерированные `tlbimp` interop-сборки. Все спайки до Фазы 3. + +## Definition of Done для v1 + +Агент через MCP: подключается к КОМПАС → создаёт деталь → строит эскиз → выдавливает → +делает снимок → видит результат → выбирает грань → повторяет → сохраняет `.m3d`. +Сервер собирается в один `.exe` и регистрируется в клиенте по stdio. diff --git a/docs/presentation.html b/docs/presentation.html new file mode 100644 index 0000000..54f7aef --- /dev/null +++ b/docs/presentation.html @@ -0,0 +1,344 @@ + + + + + +КОМПАС-3D MCP — управление CAD через LLM + + + + + +
+
+ Статус: спайки S1–S2 пройдены · подключение к КОМПАС работает +

КОМПАС-3D MCP-сервер
управление CAD языком LLM

+

MCP-сервер, который превращает операции КОМПАС-3D — создание документов, + эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом — + COM Automation API АСКОН.

+
+ .NET 8 · C# + транспорт stdio + COM Automation · API7 / API5 + Windows x64 + КОМПАС-3D v24 Home + 👁 зрение агента +
+
+
+ + +
+
+
Возможности
+

Что умеет MCP-сервер

+

Инструменты сгруппированы вокруг живого инженерного цикла моделирования — + от подключения к КОМПАС до построения и опроса детали.

+
+
+
🔌
+

Подключение к КОМПАС

+

Attach к запущенному или launch нового экземпляра по ProgID, переход на API7 + через ksGetApplication7(), управление видимостью окна.

+
+
+
📄
+

Документы

+

Создание, открытие, сохранение и закрытие деталей, сборок, чертежей и фрагментов + через IDocuments.

+
+
+
✏️
+

Эскизы

+

Эскиз на плоскости или грани, примитивы (линия, окружность, дуга, + прямоугольник), безопасный цикл BeginEdit → EndEdit.

+
+
+
🧊
+

3D-операции

+

Выдавливание и вырез, вращение, перестроение. Управление направлением, глубиной и + типом окончания (ksEndTypeEnum).

+
+
+
🎯
+

Выборка и запросы

+

Выбор грани точкой для следующего эскиза, перечисление тел и граней, + массово-центровочные характеристики детали.

+
+
+
👁️
+

Зрение агента

+

Рендер модели в PNG прямо в память (resultArrayBytes) и возврат как + image-контент MCP — агент видит результат и строит пошагово. Плюс set_view: + ориентация и zoom-to-fit перед снимком.

+
+
+
🛟
+

Escape-hatch

+

Прямой вызов команд КОМПАС через ExecuteKompasCommand для того, + что ещё не обёрнуто в типизированные инструменты.

+
+
+
+
+ + +
+
+
Ядро сценария
+

Цикл «эскиз → операция → эскиз»

+

Главный пользовательский поток v1 — итеративное параметрическое моделирование + с визуальной проверкой: рисуем профиль, превращаем в объём, смотрим на результат, + выбираем грань, рисуем следующий профиль.

+
+
01

Документ

создать деталь (part)

+
+
02

Эскиз

на плоскости XOY, профиль

+
+
03

Выдавить

boss-операция → тело

+
+
04 👁

Снимок

агент видит результат

+
+
05

Выбрать грань

по точке на теле

+
+
06

Вырез

новый эскиз → cut

+
+

Повторять со зрением — снимок → новая грань → новый эскиз → новая операция → снова снимок, пока деталь не готова

+
+
+ + +
+
+
Технологии
+

Стек решения

+

Выбор обоснован первоклассным COM-интеропом .NET и официальным C# MCP SDK — + подробности в документе архитектуры.

+
+
Платформа
.NET 8C# · win-x64
+
MCP SDK
ModelContextProtocolтранспорт stdio
+
Интероп
COM AutomationAPI7 + API5 (.tlb)
+
Потоки
STA-потокнасос сообщений
+
+
+
+ + +
+
+
Прогресс
+

TODO проекта

+

Честный срез состояния: фундамент подготовки готов, реализация сервера — + впереди.

+ +
+
Общий прогрессСпайки пройдены · идёт фундамент v1
+
+
+ +
+ +
+

Подготовка ГОТОВО

+

Исследование, документация и инструменты разработчика

+
    +
  • Исследование SDK и COM API (API5/API7, паттерны подключения)
  • +
  • CLAUDE.md — гайд по репозиторию и API
  • +
  • Навигатор по справке: tools/kdoc.py + индекс на 26k стр.
  • +
  • Документ архитектуры docs/ARCHITECTURE.md
  • +
  • Выбор стека: .NET/C#, stdio, цикл «эскиз↔операция»
  • +
  • Решение о визуальной обратной связи (зрение агента, рендер в растр)
  • +
+
+ + +
+

Фундамент v1 В РАБОТЕ

+

Скелет сервера и связь с КОМПАС

+
    +
  • Спайк S1: interop-сборки собираются под net8.0-windows
  • +
  • Спайк S2: подключение к КОМПАС (launch/attach, IApplication, версия 24.0)
  • +
  • ~Скелет решения (git, .slnx, Directory.Build.props)
  • +
  • Маппинг перечислений (enum ↔ строка)
  • +
  • STA-поток + диспетчер + насос сообщений
  • +
  • MCP-хост на stdio, логи в stderr
  • +
  • Трансляция ошибок COM в MCP-ответы
  • +
+
+ + +
+

Инструменты v1 ДАЛЕЕ

+

Покрытие центрального цикла моделирования

+
    +
  • System: connect · set_visible · status
  • +
  • Documents: create · open · save · close · active
  • +
  • Sketch: create · add line/circle/arc/rect · close
  • +
  • Features: extrude boss/cut · revolve · rebuild
  • +
  • Query: select_face · list_bodies · get_part_info
  • +
  • Vision: model_snapshot · set_view 👁️
  • +
+
+ + +
+

Качество и поставка ДАЛЕЕ

+

Тесты, сборка, интеграция с клиентом

+
    +
  • Unit-тесты чистой логики (маппинг, валидация)
  • +
  • Интеграционные тесты с КОМПАС (категория Integration)
  • +
  • Smoke через MCP Inspector
  • +
  • Сборка kompas-mcp.exe (win-x64)
  • +
  • Регистрация stdio-сервера в конфиге клиента
  • +
+
+
+
+
+ + +
+
+
Дорожная карта
+

После v1

+

Наращиваем покрытие API волнами — от простых операций к параметрике, + черчению и сборкам.

+
+
v1 · сейчас

Цикл моделирования

Документы, эскизы, выдавливание/вырез, вращение, выборка граней

+
v2

Операции

Скругление, фаска, оболочка, ребро, лофт, кинематика, массивы

+
v3

Параметрика

Переменные IVariable7, свойства IPropertyMng, формулы

+
v4

2D-черчение

Виды, размеры, тексты, штриховки; спецификации

+
v5

Сборки и экспорт

Компоненты, сопряжения, конвертеры (STEP), HTTP/SSE-транспорт

+
+
+
+ + + + + diff --git a/libs/kompas-interop/KAPITypes.dll b/libs/kompas-interop/KAPITypes.dll new file mode 100644 index 0000000..eec63f9 Binary files /dev/null and b/libs/kompas-interop/KAPITypes.dll differ diff --git a/libs/kompas-interop/Kompas6API2D5COM.dll b/libs/kompas-interop/Kompas6API2D5COM.dll new file mode 100644 index 0000000..96f8876 Binary files /dev/null and b/libs/kompas-interop/Kompas6API2D5COM.dll differ diff --git a/libs/kompas-interop/Kompas6API3D5COM.dll b/libs/kompas-interop/Kompas6API3D5COM.dll new file mode 100644 index 0000000..cd61c17 Binary files /dev/null and b/libs/kompas-interop/Kompas6API3D5COM.dll differ diff --git a/libs/kompas-interop/Kompas6API5.dll b/libs/kompas-interop/Kompas6API5.dll new file mode 100644 index 0000000..1a72664 Binary files /dev/null and b/libs/kompas-interop/Kompas6API5.dll differ diff --git a/libs/kompas-interop/Kompas6Constants.dll b/libs/kompas-interop/Kompas6Constants.dll new file mode 100644 index 0000000..6cd3549 Binary files /dev/null and b/libs/kompas-interop/Kompas6Constants.dll differ diff --git a/libs/kompas-interop/Kompas6Constants3D.dll b/libs/kompas-interop/Kompas6Constants3D.dll new file mode 100644 index 0000000..e17fc1a Binary files /dev/null and b/libs/kompas-interop/Kompas6Constants3D.dll differ diff --git a/libs/kompas-interop/KompasAPI7.dll b/libs/kompas-interop/KompasAPI7.dll new file mode 100644 index 0000000..3acacf4 Binary files /dev/null and b/libs/kompas-interop/KompasAPI7.dll differ diff --git a/src/Kompas.Mcp.ConnectSpike/Kompas.Mcp.ConnectSpike.csproj b/src/Kompas.Mcp.ConnectSpike/Kompas.Mcp.ConnectSpike.csproj new file mode 100644 index 0000000..5c04bcb --- /dev/null +++ b/src/Kompas.Mcp.ConnectSpike/Kompas.Mcp.ConnectSpike.csproj @@ -0,0 +1,33 @@ + + + + Exe + kompas-connect-spike + Kompas.Mcp.ConnectSpike + + + + + + $(KompasInteropDir)\KompasAPI7.dll + true + false + + + $(KompasInteropDir)\Kompas6API5.dll + true + false + + + $(KompasInteropDir)\Kompas6Constants.dll + true + false + + + $(KompasInteropDir)\Kompas6Constants3D.dll + true + false + + + + diff --git a/src/Kompas.Mcp.ConnectSpike/Program.cs b/src/Kompas.Mcp.ConnectSpike/Program.cs new file mode 100644 index 0000000..67ffba0 --- /dev/null +++ b/src/Kompas.Mcp.ConnectSpike/Program.cs @@ -0,0 +1,137 @@ +// Спайк S1+S2: проверка interop-сборок и подключения к КОМПАС-3D. +// +// Доказывает, что: +// * вендорские interop-сборки (KompasAPI7 / Kompas6API5) собираются и загружаются под net8.0-windows; +// * клиент умеет attach к запущенному КОМПАС и launch нового экземпляра; +// * получает IApplication (API7), делает окно видимым и читает версию. +// +// Внимание: Marshal.GetActiveObject отсутствует в .NET 5+, поэтому attach делается +// через нативные CLSIDFromProgID + GetActiveObject (oleaut32). +// +// Логи -> stderr, итог -> stdout. Код возврата 0 = успех. + +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Kompas6API5; +using KompasAPI7; + +namespace Kompas.Mcp.ConnectSpike; + +[SupportedOSPlatform("windows")] +internal static class Program +{ + // Порядок попыток подключения. На v24 Home проверено: есть .7 и .5, нет LT. + private static readonly string[] ProgIds = + { + "KOMPAS.Application.7", // прямой API7 + "KOMPAS.Application.5", // API5 -> ksGetApplication7() + "KOMPASLT.Application.5", // запасной (LT-инсталляции) + }; + + private static int Main() + { + Log("=== KOMPAS connect spike ==="); + bool launchedByUs = false; + IApplication? app = null; + + // 1) attach к уже запущенному экземпляру + foreach (var id in ProgIds) + { + var root = AttachByProgId(id); + if (root is null) continue; + app = AsApplication(root); + if (app != null) { Log($"attached: {id}"); break; } + } + + // 2) запустить новый экземпляр + if (app is null) + { + foreach (var id in ProgIds) + { + var root = CreateByProgId(id); + if (root is null) continue; + app = AsApplication(root); + if (app != null) { launchedByUs = true; Log($"launched: {id}"); break; } + } + } + + if (app is null) + { + Log("РЕЗУЛЬТАТ: подключиться к КОМПАС не удалось."); + return 1; + } + + app.Visible = true; + Console.WriteLine("CONNECTED"); + ReportVersion(app); + + Log(launchedByUs + ? "Экземпляр запущен этим процессом (в реальном сервере здесь решаем про Quit при выходе)." + : "Подключились к уже запущенному экземпляру (Quit не вызываем)."); + Log("Готово. Окно КОМПАС должно быть видимым."); + return 0; + } + + /// Привести COM-корень к IApplication: API7 — прямой каст, API5 — через ksGetApplication7(). + private static IApplication? AsApplication(object root) + { + if (root is IApplication direct) + return direct; + + if (root is KompasObject ko) + { + try { return ko.ksGetApplication7() as IApplication; } + catch (Exception ex) { Log($"ksGetApplication7 failed: {ex.Message}"); } + } + + try { return (IApplication)root; } + catch { return null; } + } + + private static void ReportVersion(IApplication app) + { + try { Console.WriteLine($"BuildNumber: {app.BuildNumber}"); } + catch (Exception ex) { Log($"BuildNumber failed: {ex.Message}"); } + + try + { + int major = 0, minor = 0, release = 0, build = 0; + app.GetSystemVersion(out major, out minor, out release, out build); + Console.WriteLine($"Version: {major}.{minor}.{release} (build {build})"); + } + catch (Exception ex) { Log($"GetSystemVersion failed: {ex.Message}"); } + } + + private static object? AttachByProgId(string progId) + { + try + { + CLSIDFromProgID(progId, out Guid clsid); + GetActiveObject(ref clsid, IntPtr.Zero, out object obj); + return obj; + } + catch + { + return null; // не запущен / не зарегистрирован + } + } + + private static object? CreateByProgId(string progId) + { + var type = Type.GetTypeFromProgID(progId, throwOnError: false); + if (type is null) return null; + try { return Activator.CreateInstance(type); } + catch (Exception ex) { Log($"CreateInstance({progId}) failed: {ex.Message}"); return null; } + } + + [DllImport("ole32.dll", PreserveSig = false)] + private static extern void CLSIDFromProgID( + [MarshalAs(UnmanagedType.LPWStr)] string progId, out Guid clsid); + + [DllImport("oleaut32.dll", PreserveSig = false)] + private static extern void GetActiveObject( + ref Guid rclsid, IntPtr pvReserved, + [MarshalAs(UnmanagedType.IUnknown)] out object ppunk); + + private static void Log(string msg) => Console.Error.WriteLine(msg); +} diff --git a/tools/kdoc.py b/tools/kdoc.py new file mode 100644 index 0000000..b2eca62 --- /dev/null +++ b/tools/kdoc.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +kdoc — навигация по справке SDK КОМПАС-3D (Help & Manual WebHelp) для агента. + +Справка — это ~26 000 HTML-страниц в docs/KOMPAS_SDK_ru-RU/ со «зашумлённой» +вёрсткой (шапка, подвал, JS). Листать их напрямую неэффективно. Этот инструмент: + + * build-index — один раз строит компактный индекс docs/kompas_sdk_index.tsv + (файлзаголовоккраткое описание) из zoom_pageinfo.js; + * search — ищет страницы по подстроке в заголовке/описании; + * read — печатает чистый текст одной страницы (тело топика без вёрстки). + +Типичный сценарий агента: + python tools/kdoc.py search "выдавливание" # найти нужный интерфейс + python tools/kdoc.py read ibossextrusiondefinition.html + +Поиск по индексу можно вести и встроенным Grep по docs/kompas_sdk_index.tsv — +формат строки: <имя_файла>\t<заголовок>\t<описание>. +""" +import sys +import os +import re +import json +import html +import argparse +from html.parser import HTMLParser + +# На Windows консоль часто cp1251 — принудительно выводим UTF-8, +# чтобы кириллица не искажалась при вызове из агента/любого шелла. +for _stream in (sys.stdout, sys.stderr): + try: + _stream.reconfigure(encoding="utf-8") + except (AttributeError, ValueError): + pass + +HERE = os.path.dirname(os.path.abspath(__file__)) +DOC_DIR = os.environ.get( + "KDOC_DIR", os.path.join(HERE, "..", "docs", "KOMPAS_SDK_ru-RU") +) +DOC_DIR = os.path.abspath(DOC_DIR) +INDEX_PATH = os.path.abspath(os.path.join(DOC_DIR, "..", "kompas_sdk_index.tsv")) +PAGEINFO = os.path.join(DOC_DIR, "zoom_pageinfo.js") + +# Текстовый «мусор» навигации/подвала, попадающий в тело топика. +NOISE = { + "please enable javascript to view this site.", + "scroll", "предыдущий", "вверх", "следующий", + "содержание", "содержане", "указатель", "поиск", + "подразделы:", "(отсутствуют)", +} +FOOTER_RE = re.compile(r"©?\s*ООО\s*«?АСКОН") + + +def build_index() -> int: + """Разобрать массив pagedata из zoom_pageinfo.js в TSV-индекс.""" + if not os.path.isfile(PAGEINFO): + sys.exit(f"Не найден {PAGEINFO}. Проверьте KDOC_DIR / путь к справке.") + with open(PAGEINFO, "r", encoding="utf-8", errors="replace") as f: + text = f.read() + marker = "pagedata = " + pos = text.find(marker) + if pos < 0: + sys.exit("В zoom_pageinfo.js не найден массив pagedata.") + payload = text[pos + len(marker):].strip() + payload = payload.rstrip(";").strip() + data = json.loads(payload) # [["./file.html","title","desc","img"], ...] + + rows = [] + for entry in data: + if not entry or not entry[0]: + continue + fname = entry[0].lstrip("./").strip() + title = html.unescape((entry[1] or "").strip()) + desc = html.unescape((entry[2] or "").strip()) + title = re.sub(r"\s+", " ", title) + desc = re.sub(r"\s+", " ", desc)[:300] + rows.append(f"{fname}\t{title}\t{desc}") + + with open(INDEX_PATH, "w", encoding="utf-8", newline="\n") as f: + f.write("\n".join(rows) + "\n") + print(f"Индекс записан: {INDEX_PATH} ({len(rows)} страниц)") + return len(rows) + + +def search(terms, limit=40): + """Грубый поиск по индексу: AND по всем подстрокам, без регистра.""" + if not os.path.isfile(INDEX_PATH): + print("Индекс отсутствует — строю...", file=sys.stderr) + build_index() + needles = [t.lower() for t in terms if t.strip()] + hits = 0 + with open(INDEX_PATH, "r", encoding="utf-8") as f: + for line in f: + low = line.lower() + if all(n in low for n in needles): + parts = line.rstrip("\n").split("\t") + fname = parts[0] + title = parts[1] if len(parts) > 1 else "" + print(f"{fname}\t{title}") + hits += 1 + if hits >= limit: + print(f"... (показаны первые {limit}; уточните запрос)") + break + if hits == 0: + print("Ничего не найдено.") + + +class _TopicExtractor(HTMLParser): + """Собирает текст из
, пропуская script/style.""" + BLOCK = {"p", "div", "br", "tr", "li", "h1", "h2", "h3", "h4", + "table", "pre"} + + def __init__(self): + super().__init__(convert_charrefs=True) + self.depth = 0 # глубина вложенности внутри topicbody + self.capturing = False + self.skip = 0 # внутри script/style + self.out = [] + + def handle_starttag(self, tag, attrs): + attrd = dict(attrs) + if not self.capturing and attrd.get("id") == "topicbody": + self.capturing = True + self.depth = 1 + return + if not self.capturing: + return + if tag in ("script", "style"): + self.skip += 1 + if tag in self.BLOCK: + self.out.append("\n") + if tag != "br": + self.depth += 1 + + def handle_endtag(self, tag): + if not self.capturing: + return + if tag in ("script", "style") and self.skip: + self.skip -= 1 + if tag != "br": + self.depth -= 1 + if self.depth <= 0: + self.capturing = False + + def handle_data(self, data): + if self.capturing and not self.skip: + self.out.append(data) + + +def read(fname): + path = os.path.join(DOC_DIR, os.path.basename(fname)) + if not path.endswith(".html"): + path += ".html" + if not os.path.isfile(path): + sys.exit(f"Нет файла: {path}") + with open(path, "r", encoding="utf-8", errors="replace") as f: + raw = f.read() + p = _TopicExtractor() + p.feed(raw) + text = "".join(p.out) + + cleaned = [] + for line in text.splitlines(): + s = re.sub(r"[ \t ]+", " ", line).strip() + if not s: + continue + if s.lower() in NOISE: + continue + if FOOTER_RE.search(s): + break # подвал — дальше только копирайт/телефон + cleaned.append(s) + print(os.path.basename(path)) + print("=" * len(os.path.basename(path))) + print("\n".join(cleaned).strip()) + + +def main(): + ap = argparse.ArgumentParser( + prog="kdoc", description="Навигация по справке SDK КОМПАС-3D для агента." + ) + sub = ap.add_subparsers(dest="cmd", required=True) + sub.add_parser("build-index", help="построить docs/kompas_sdk_index.tsv") + sp = sub.add_parser("search", help="искать по заголовку/описанию") + sp.add_argument("terms", nargs="+") + sp.add_argument("--limit", type=int, default=40) + rp = sub.add_parser("read", help="печать чистого текста страницы") + rp.add_argument("file") + args = ap.parse_args() + + if args.cmd == "build-index": + build_index() + elif args.cmd == "search": + search(args.terms, args.limit) + elif args.cmd == "read": + read(args.file) + + +if __name__ == "__main__": + main()