# Открытые вопросы для проработки Журнал спорных моментов и решений, принятых автономно (ночная сессия 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`); добавить текст последней ошибки КОМПАС. ### Ревью v2 (грань/вращение/скругление/фаска/габариты/список граней) **⚠️ Отложено:** - v2-1 — **направление операций на грани**: для бобышки/выреза на выбранной грани параметр `forward` зависит от ориентации нормали грани, которая для эскиза-на-грани оказывается направленной *внутрь* тела (boss с `forward=true` уходит в материал — прироста нет; наружу даёт `forward=false`). Сейчас выбор направления — на стороне агента (по снимку). На проработку: автоопределение «наружу» по нормали грани, либо опция `auto`. - v2-2 — **утечки транзитных RCW в `PartModeler`**: `GetTopPart()` и коллекции `EntityCollection(o3d_face/o3d_edge)` не освобождаются (унаследованный паттерн; `QueryService` их освобождает, `PartModeler` — только зарегистрированные сущности в `ResetCore`). Накапливается за длинную сессию. На проработку: единый `using`/release транзитов в построителе. Тот же паттерн у новых членов (`SelectFaceByIndex`, `ShellAsync`: транзитные `ksShellDefinition`/ `FaceArray()`/коллекции граней не освобождаются — сознательно, ради консистентности с `Fillet`/`Chamfer`; войдёт в общий рефакторинг release транзитов). - v2-3 — **стабильность индексов граней** (`list_faces` → `sketch_create_on_face_index`): индекс валиден, пока геометрия не меняется между вызовами. После любой операции порядок/состав граней может измениться — переиспользовать старый индекс нельзя, нужно перезапросить `list_faces`. - v2-4 — `fillet/chamfer` строятся по одному ребру (первое из `SelectByPoint`/коллекции); `tangent=false`. На проработку: набор рёбер, скругление по касательным, переменный радиус. **✅ Решено по ходу:** - Угол вращения `revolve_*` — в градусах, `directionType=dtNormal`, `toroidShapeType=сфероид`, `SetThinParam(false,…)` для сплошного тела. Ось вращения — осевая линия (системный стиль 3). - Габариты — `ksPart.GetGabarit(full=false)` (только тела), координаты в мм. ## Журнал решений по ходу (дополняется автоматически во время работы) ### ⚠️ Конкурентность вызовов 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): API5 `CalcMassInertiaProperties`, а не API7 Сначала пробовали API7 `IMassInertiaParam7` (от `TopPart`). Две проблемы: 1. **Единицы** — отдаёт в единицах отображения МЦХ документа (по умолчанию см³/см²/г), зависит от настроек. 2. **Залипающий кэш** — после первого расчёта свойство `Actual` остаётся `TRUE` и не сбрасывается при изменении геометрии через API5; `Calculate()` возвращает `FALSE` и не пересчитывает. Сценарий «запрос → достройка → запрос» отдавал **устаревшее** значение. Сброс через `RebuildModel`, переключение `MassSettingMode` — не помогли. **Решение (✅ в коде):** считаем через API5 **`ksPart.CalcMassInertiaProperties(bitVector)`** → `ksMassInertiaParam` (`v`/`m`/`F`/`xc`/`yc`/`zc`). Считает «по запросу» (без залипания), единицы задаются битовым вектором: `ST_MIX_MM (0x1) | ST_MIX_KG (0x10)` → **мм³, мм², кг, мм**. Согласовано с тем, что деталь строится через API5 `ksPart`. Проверено двумя тестами: цилиндр R10×H20 (V≈6283 мм³, S≈1885 мм², m≈0.0494 кг, Zc=10 мм) и «запрос → штифт на грани → запрос» (виден корректный прирост ≈785 мм³). Материал по умолчанию — сталь (ρ≈7.856 г/см³). ### ✅ Снимок 3D: рендер через файл, а не resultArrayBytes `ksDocument3D.SaveAsToRasterFormat(fileName, param)` при **непустом** `fileName` возвращает TRUE, пишет корректный PNG на диск, но `param.resultArrayBytes` остаётся **пустым**. Поэтому `SnapshotService` рендерит во временный файл и читает байты обратно (надёжно проверено на v24 Home). **На проработку:** пустое имя файла + `returnResultAsArrayBytes=true` для чистого in-memory — не проверено; текущий путь через temp-файл работает. ### ✅ STEP импорт/экспорт и list_components — реализованы `import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (`QueryService.ListComponentsAsync`) добавлены в v2+. Проверенные COM-приёмы: - Импорт: `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (код формата, не путь к DLL) → `IConverter.ConverterParameters(cmd)` → `IAdditionConvertParameters.Format=ksConverterFromSTEP` → `IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`. - Экспорт: `ConvertToAdditionFormat` с форматами AP203/AP214/AP242 (`StepFormat` enum в `Core/Conversion/StepFormat.cs`). - Param-коклассы (AdditionConvertParameters) — **не** CoCreatable, только через фабрики. - Обход сборки: `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`. - Извлечение детали: `prm.NeedCreateComponentsFiles=true` + `IPart7.OpenSourceDocument`. ### ✅ Прямое редактирование импортированной B-rep — move_face реализован **Решено.** Перемещение грани продуктизировано как инструмент `move_face` / сервис `FaceEditService` (`src/Kompas.Mcp.Core/Editing/FaceEditService.cs`). Зарегистрирован в DI (`Program.cs`); интеграционный тест — `Integration/FaceEditTests.cs`. Проверенный паттерн: - Объект грани берётся через **`IPart7.FindObjectsByPoint(x, y, z, true)`** → результат приводится к `KompasAPI7.IFace`. Не требует `EntityCollection`/`SelectByPoint` через API5. - Контейнеры операций не создаются отдельно: `(ISurfaceContainer)part` и `(IModelContainer)part` — COM-QI от `IPart7` в рантайме. - `FaceMover`: `SetFaces(faces)` + `Offset(distance)` + `Direction(true/false = наружу/внутрь)` + `Update()`. - Работает как на параметрической геометрии, так и на импортированной B-rep (проверено: деталь top_spacer, высота 39.45 → 41.45 мм). Полный «рассечь→раздвинуть→объединить» (`SplitSolids`/`BodyRepositions`) — механика есть и драйвится, но отдельными MCP-инструментами не оформлена (возможная будущая опция). ### ✅ Структурный осмотр модели — инспекция реализована **Решено.** 7 инструментов (`InspectionTools.cs`, `ModelInspectionService.cs`, `InspectionText.cs`): - `describe_model` — структурный «паспорт» детали: габарит, МЦХ, тела, сводка топологии, дерево операций с параметрами, переменные. **Предпочтителен перед `model_snapshot`**. - `list_features` / `list_bodies` / `list_variables` — детальные срезы. - `describe_face` / `describe_edge` — drill-down по индексу. - `measure` — расстояние/угол между объектами (`ksMeasurer`). Ключевые паттерны: дерево — `ksPart.GetFeature()` → `SubFeatureCollection(true,false)`; тела — `BodyCollection()` → `ksBody`; переменные — `VariableCollection()` → `ksVariable`. Детект «импорт без истории»: `bodyCount>0 && нет формообразующих операций && features.Count<=bodyCount+1`. Итог: **62 инструмента, 101 тест.** ### ✅ hole — реализован через API7 **Решено.** Инструмент `hole` добавлен как первая формообразующая операция на API7 — в API5 интерфейс `ksHoleDefinition` отсутствует в interop (аналогично `draft`; `o3d_holeOperation=52` есть, но определения нет). Новый сервис `HoleService` (`src/Kompas.Mcp.Core/Modeling/HoleService.cs`) не является методом `PartModeler`. Паттерн размещения: `(IModelContainer)part7` → `Points3D.Add()` (создаёт `IPoint3D` как центр) → `Holes3D.Add()` → `IHole3D` → `(IHoleDisposal)hole` (`BaseSurface` = грань по `FindObjectsByPoint`, `Perpendicular=true`, `AssociationVertex=point`) → `Update()` → `RebuildDocument()`. Эскиз размещения не нужен. **Ключевая тонкость — направление по объёму:** `Update()` возвращает TRUE даже если сверлит «в воздух» снаружи тела (убыли материала нет). Решение: попытка `Direction=true` → Rebuild → сравниваем объём через API5 `CalcMassInertiaProperties`; если не убыл — пробуем `Direction=false`. Тот же класс проблем, что у boss/cut на грани (непредсказуемая ориентация нормали). При неуспехе обеих попыток — откат через `IFeature7.Delete()`. Добавлена валидация `double.IsFinite` (Infinity проходил мимо `>0`). Отверстие не попадает в реестр `_features` API5 → возвращает подтверждение без id. +3 интеграционных теста (`HoleTests.cs`). Итог: **63 инструмента, 104 теста (60 unit + 44 integration).**