8c560fd0f6
docs: ревью v2 в OPEN_QUESTIONS (направление операций, индексы граней, утечки RCW) Зафиксированы наблюдения по новому слою v2 и отложенные на проработку пункты (v2-1 направление boss/cut на грани зависит от нормали; v2-2 транзитные RCW в PartModeler; v2-3 стабильность индексов граней; v2-4 fillet/chamfer по одному ребру), а также принятые по ходу решения (углы вращения, габариты). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> @
115 lines
11 KiB
Markdown
115 lines
11 KiB
Markdown
# Открытые вопросы для проработки
|
||
|
||
Журнал спорных моментов и решений, принятых автономно (ночная сессия 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 транзитов в построителе.
|
||
- 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-файл работает.
|