Новый слой «модель как данные» (приоритетный над model_snapshot): - describe_model — структурный «паспорт» детали одним вызовом (габарит, МЦХ, тела, сводка топологии, дерево построения с параметрами, переменные) - list_features / list_bodies / list_variables - describe_face / describe_edge (drill-down по индексу) - measure (расстояние/угол между гранями/рёбрами/вершинами) - model_snapshot понижен до fallback для визуально-пространственных вопросов Реализация: ModelInspectionService (COM) + чистый InspectionText (рендер, группировка, детект «импорт без истории») + record-модели; InspectionTools. Дерево читается через ksPart.GetFeature()->SubFeatureCollection; точный тип и параметры операций берутся из GetObject()->GetDefinition() (ksFeature.type отдаёт лишь coarse o3d_entity). Снимок дерева также включает слои conversion (STEP), editing (move_face/solid) и validation, развивавшиеся параллельно. Полный прогон: 61 тест зелёный, 43 MCP-инструмента. Документация (README, ARCHITECTURE, OPEN_QUESTIONS, CLAUDE.md, presentation.html) синхронизирована. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
Открытые вопросы для проработки
Журнал спорных моментов и решений, принятых автономно (ночная сессия 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). Две проблемы:
- Единицы — отдаёт в единицах отображения МЦХ документа (по умолчанию см³/см²/г), зависит от настроек.
- Залипающий кэш — после первого расчёта свойство
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 (StepFormatenum в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. Итог: 43 инструмента, 60 тестов.