# Открытые вопросы для проработки Журнал спорных моментов и решений, принятых автономно (ночная сессия 2026-05-25/26). Помечено: ⚠️ = требует вашего решения; ✅ = решено по умолчанию, можно пересмотреть. --- ## ⚠️ 1. API5 vs API7 для построения 3D **Контекст.** Примеры SDK строят 3D через **API5** (`ksPart.NewEntity(o3d_*)` → `ksBossExtrusionDefinition` → `Create()`). API7 предлагает `IPart7` → `IModelContainer` → `IExtrusions.Add()`. **Решение по умолчанию (✅ в коде):** в v1 строю 3D через **API5 `ksPart`** (надёжно, повторяет рабочие примеры Step3d1). API7 использую для приложения/документов/версии. **На проработку:** переводить ли операции на чистый API7 позже (плюс — единообразие, минус — риск). ## ✅ 2. Точка входа подключения — API5 Для построения 3D нужен `KompasObject` (API5), а для снимка — `ksDocument3D` (API5). Поэтому `KompasSession` подключается через `KOMPAS.Application.5` → `KompasObject`, затем `ksGetApplication7()` → `IApplication`. Держит обе ссылки. (Ранее в спайке заходили через `.7`.) ## ✅ 3. Состав проектов v1 Вместо 4 проектов (Host/Tools/Core/Interop) сделано компактно: **Core** (COM + сервисы) + **Host** (MCP + определения инструментов) + **Tests**. Interop — вендорские DLL в `libs/`. Разнести на больше проектов можно позже. ## ✅ 4. Тестовые артефакты Тестовые документы КОМПАС сохраняю в `.scratch/` (gitignore). Снимки рендера — туда же, для визуальной проверки. --- ## Само-ревью кода (ночная сессия) — итоги **Исправлено сразу:** - C1/C2 — утечки COM и устаревшие id: `PartModeler` освобождает RCW и сбрасывает реестр (`ResetAsync`/`Dispose`), `DocumentTools` сбрасывает реестр при create/open/close; `DocumentService` освобождает транзитные `IDocuments`/`IKompasDocument`. - M3 — `KompasSession.Dispose`: ограниченное ожидание (5 c) вместо вечного блока. - M4 — `ExtrudeAsync`: убран `dynamic`, конкретные `ksBossExtrusionDefinition`/ `ksCutExtrusionDefinition`; `throughAll` разрешён только для выреза. - m7 — `extrude_cut`: дефолт `throughAll=false`, без молчаливой подмены `depth`. - Регрессионный тест: после `ResetAsync` старый id эскиза недействителен. **⚠️ Отложено на проработку (из ревью):** - M5 — отмена/насос сообщений: `CancellationToken` не прерывает уже идущий COM-вызов; зависший вызов (модальный диалог КОМПАС) блокирует всю очередь. Возможное решение — диспетчер с насосом сообщений + подавление диалогов. Связано с [конкурентностью MCP]. - m6 — `colorBPP=24` для снимка: для GIF (палитра) может не подойти; снимок по умолчанию PNG. - m8 — приведения enum к `short` для `GetPart/NewEntity/GetDefaultEntity`: на v24 работает (тесты зелёные), но стоит сверить точные типы параметров в interop. - m10 — `_app/_kompas` читаются из `IsConnected/Status()` вне STA-потока без `volatile` (доброкачественное устаревшее чтение). - m11 — трансляция `COMException`/HRESULT в понятные сообщения (сейчас понятны только наши `InvalidOperationException`); добавить текст последней ошибки КОМПАС. ## Журнал решений по ходу (дополняется автоматически во время работы) ### ⚠️ Конкурентность вызовов MCP и состояние сессии MCP-сервер обрабатывает запросы **конкурентно**. Цикл моделирования с реестром эскизов по id (`sketch_create` → `sketch_add_*`) предполагает, что клиент **ждёт ответа** на каждый вызов перед следующим (нормальное поведение LLM-клиентов). При «конвейерной» отправке зависимых вызовов возможна гонка (id ещё не зарегистрирован). STA-диспетчер сериализует COM, но не порядок постановки задач. **На проработку:** нужна ли серверная сериализация вызовов инструментов (очередь) для надёжности stateful-сессии? Пока полагаемся на последовательное поведение клиента. ### ✅ Возврат изображения: ImageContentBlock.FromBytes Поле `ImageContentBlock.Data` хранит **base64-байты**, а не сырые. Сырые байты PNG нужно передавать через `ImageContentBlock.FromBytes(bytes, mimeType)` — он сам кодирует. Прямое присваивание `Data = rawBytes` портит изображение на проводе. Исправлено в VisionTools. ### ✅ МЦХ (get_part_info): единицы измерения и поведение Calculate() `IMassInertiaParam7` по умолчанию возвращает значения в **единицах отображения МЦХ** документа (по умолчанию см³/см²/г, плотность г/см³) — то есть зависит от настроек, а не фиксированные СИ. Поэтому `QueryService` принудительно выставляет `LengthUnits = ksLUnMM` и `MassUnits = ksMUnKG` перед чтением → детерминированные **мм³, мм², кг, мм**. Проверено тестом (цилиндр R10×H20: V≈6283 мм³, S≈1885 мм², m≈0.0494 кг, Zc=10 мм). Тонкость: `Calculate()` возвращает **FALSE даже при успехе** (когда расчёт уже актуален после `RebuildDocument`), поэтому опираемся на свойство `Actual`, а не на возвращаемое значение. Материал по умолчанию — сталь (ρ≈7.856 г/см³); масса зависит от назначенного материала. ### ✅ Снимок 3D: рендер через файл, а не resultArrayBytes `ksDocument3D.SaveAsToRasterFormat(fileName, param)` при **непустом** `fileName` возвращает TRUE, пишет корректный PNG на диск, но `param.resultArrayBytes` остаётся **пустым**. Поэтому `SnapshotService` рендерит во временный файл и читает байты обратно (надёжно проверено на v24 Home). **На проработку:** пустое имя файла + `returnResultAsArrayBytes=true` для чистого in-memory — не проверено; текущий путь через temp-файл работает.