Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-assembly-add-component-design.md
T
mikhail 5f17884f06 docs: спек assembly_add_component — учтены 8 замечаний ревью Codex
Проверки COM-результатов (AddFromFile/SetOrigin/UpdatePlacement), helper
RequireActiveAssembly (раздельные ошибки, проверка типа документа до TopPart),
валидация расширения .m3d/.a3d + нормализация пути, before/after count в тесте,
фиксированная геометрия детали от нуля, проверка сохранённости файла.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:57:04 +03:00

12 KiB
Raw Blame History

Дизайн: вставка компонента в сборку (assembly_add_component) через API7

Дата: 2026-05-27 Статус: спроектировано (сигнатуры подтверждены рефлексией interop + kompas-sdk-research); ревью Codex учтено (8 замечаний внесены)

Цель

Открыть новый класс функционала — СБОРКИ. До сих пор сделана только геометрия ДЕТАЛИ. Первый инкремент вехи: вставить деталь из сохранённого .m3d-файла в активную сборку с заданием положения. Это фундамент — без вставки компонентов сборка пуста.

list_components (чтение сборки) уже есть; теперь учимся её НАПОЛНЯТЬ.

Архитектурное решение

Сборки — это НЕ PartModeler (он строит API5-деталь ksPart). Заводим новый сервис AssemblyService (API7, паттерн как у HoleService/FaceEditService: контейнеры берём COM-QI, операции на STA-потоке KompasDispatcher) и новый класс инструментов AssemblyTools.

  • src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs — сервис.
  • src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs — чистые static-валидаторы (unit-тест).
  • src/Kompas.Mcp.Core/Assemblies/AddedComponent.cs — record результата (Index, Name).
  • src/Kompas.Mcp.Host/Tools/AssemblyTools.cs — MCP-инструмент.
  • DI: AddSingleton<AssemblyService>() в Program.cs.

(Namespace Kompas.Mcp.Core.Assemblies — во множественном числе, чтобы не конфликтовать с System.Reflection.Assembly.)

Подтверждённый workflow (рефлексия interop + справка)

Рефлексией KompasAPI7.dll подтверждено:

  • IParts7.AddFromFile(string FileName, bool ExternalFile, bool Redraw)Part7.
  • IPart7.Placement (Placement3D), IPart7.UpdatePlacement(bool Redraw).
  • IPlacement3D.SetOrigin(double X, double Y, double Z).
// RequireActiveAssembly (см. ниже) — раздельные проверки до доступа к TopPart:
IKompasDocument doc = app.ActiveDocument;                          // != null
// doc.DocumentType == ksDocumentAssembly  (деталь тоже IKompasDocument3D — отсечь по типу!)
IKompasDocument3D doc3d = (IKompasDocument3D)doc;
IPart7  top   = (IPart7)doc3d.TopPart;                             // != null
IParts7 parts = top.Parts;

IPart7 part = parts.AddFromFile(filePath, /*ExternalFile*/ true, /*Redraw*/ true) as IPart7
    ?? throw;   // проверяем результат
// ExternalFile=true: компонент — ссылка на внешний .m3d (что нам и нужно — отдельный файл-источник).
// Redraw=true: вставить и перерисовать ИСХОДНОЕ положение (в начало координат сборки).

IPlacement3D pl = part.Placement as IPlacement3D ?? throw;         // read-only свойство, мутируем
if (!pl.SetOrigin(x, y, z)) throw;                                 // мировые координаты сборки, мм
if (!part.UpdatePlacement(/*Redraw*/ true)) throw;                 // именно это ПРИМЕНЯЕТ перемещение

Проверки результатов (как в HoleService): AddFromFile приводится к IPart7 (иначе ошибка); Placement приводится к IPlacement3D; SetOrigin(...)==true; UpdatePlacement(true)==true. Неуспех любого — InvalidOperationException с понятным текстом. (Откат «осиротевшего» компонента при неуспехе позиционирования — best-effort через part.Owner?.Delete(); на практике origin-сдвиг надёжен, но проверки обязательны.)

Redraw ≠ применение перемещения (важно): AddFromFile(..., Redraw:true) вставляет и перерисовывает компонент в ИСХОДНОМ положении. Само перемещение применяет и проверяет именно part.UpdatePlacement(true) — не Redraw. Отдельный doc3d.RebuildDocument() в базовом workflow не нужен (интеграционный тест подтверждает габарит после UpdatePlacement).

Почему AddFromFile, а не AddFromFileWithParam: для первого инкремента нужен только сдвиг origin. AddFromFile + Placement.SetOrigin + UpdatePlacement — минимальный надёжный путь (подтверждён рефлексией: все три члена есть в interop). AddFromFileWithParam требует получить IPlacement3D/IInsertPartParameters через IKompasDocument1.GetInterface(код) до вставки — это лишняя неочевидность (коды 11223/13196 не проверены рефлексией) и YAGNI на этом шаге. Ориентацию (SetVector/углы Эйлера) и параметры вставки добавим в следующих инкрементах при необходимости.

Единицы: x/y/z — миллиметры в мировой СК сборки. По умолчанию (0,0,0) компонент встаёт в начало координат сборки.

Фиксация: через API первый компонент НЕ фиксируется автоматически. На этом шаге фиксацию НЕ выставляем (YAGNI) — для теста объёмом/габаритом она не нужна; добавим параметр fixed позже, если понадобится.

MCP-инструмент (новая группа Assembly)

Инструмент Параметры Поведение
assembly_add_component filePath: string, x=0, y=0, z=0 Вставить деталь/подсборку из .m3d/.a3d-файла в активную сборку; origin компонента — (x,y,z) мм. Возвращает индекс и имя вставленного компонента.

Контракт: активный документ должен быть сборкой (иначе понятная ошибка); файл должен существовать. Возврат — текст вида «Компонент '<имя>' вставлен в сборку, индекс N, позиция (x,y,z)».

Валидация (чистые static, unit-тест)

AssemblyValidation:

  • NormalizeComponentPath(string? path) → stringTrim → проверка непустоты → проверка расширения (.m3d/.a3d, case-insensitive; иначе ArgumentException) → Path.GetFullPath (вернуть абсолютный путь). Чистая (не трогает ФС) → unit-тест.
  • RequireFinitePosition(double x, double y, double z) — координаты конечны (double.IsFinite; отсекает Infinity/NaN, как в HoleService).

Проверка существования файла (File.Exists на нормализованном пути) — в сервисе (не чистая, не unit-тест), даёт FileNotFoundException с абсолютным путём.

Проверка активного документа (helper в сервисе)

RequireActiveAssembly() — раздельные ошибки (Codex #2):

  1. ActiveDocument == null → «Нет активного документа».
  2. doc.DocumentType != ksDocumentAssembly → «Активный документ не сборка (тип: …). Создайте сборку: document_create assembly». (Деталь — тоже IKompasDocument3D, поэтому проверка по типу обязательна ДО доступа к TopPart.)
  3. doc is not IKompasDocument3D → «Активный документ не 3D».
  4. doc3d.TopPart is not IPart7 → «Не удалось получить TopPart сборки».

Реализация

  • AssemblyService.AddComponentAsync(string filePath, double x, double y, double z, ct) — на STA-потоке: валидация → проверка типа активного документа (сборка) → AddFromFilePlacement.SetOriginUpdatePlacement → вернуть AddedComponent(Index, Name). Индекс = parts.Count - 1 после вставки; имя = part.Name.
  • Транзитные RCW не освобождаем точечно — консистентно с остальным слоем (долг v2-2).

Тестирование

Unit (AssemblyValidationTests)

  • NormalizeComponentPath бросает на null/пустой/пробельный; бросает на чужом расширении (.step/.txt); пропускает .m3d/.a3d (в т.ч. с пробелами и в разном регистре), возвращает абсолютный путь.
  • RequireFinitePosition бросает на NaN/Infinity по каждой координате; пропускает конечные.

Integration (AssemblyTests, Collection KompasCollection)

Паттерн: построить деталь → SaveAsAsync(.scratch/*.m3d)проверить File.Exists + размер>0CloseAsync → создать сборку → AddComponentAsync → проверить → finally CloseAsync. Геометрия детали фиксирована от нуля (эскиз 0..40 × 0..40, выдавить 0..20 ⇒ локальный габарит X[0,40] Y[0,40] Z[0,20]) — чтобы абсолютные ожидания по габариту были детерминированы (Codex #7).

  1. Вставка в начало координат: сохранить деталь-коробку, проверить файл, закрыть. Создать сборку. before = list_components.Count. var c = AddComponentAsync(file, 0,0,0).
    • list_components.Count == before + 1 (Codex #6); компонент по индексу c.Index: SizeX≈40, SizeY≈40, SizeZ≈20 (±допуск); c.Name непуст.
    • get_bounding_box сборки: X∈[0,40], Y∈[0,40], Z∈[0,20] (±допуск).
  2. Позиционирование: та же деталь, AddComponentAsync(file, 50, 0, 0).
    • get_bounding_box сборки: X∈[50,90] (сдвиг origin на +50 подтверждает работу позиции).

Объёмом сборку тоже можно проверить (get_part_info на TopPart агрегирует компоненты) — но габарит надёжнее и прямо проверяет позицию. Артефакты .m3d — в gitignored .scratch/.

Дальнейшие инкременты (вне этого спека)

  1. assembly_add_mate — сопряжения (IMateConstraints3D.Add(mc_Coincidence/mc_Distance/…) + BaseObject1/2 + Update). Типы: совпадение/параллельность/перпендикулярность/касательность/ концентричность/расстояние/угол/симметрия.
  2. Перемещение/массив/фиксация компонентов — по необходимости.