docs: спек assembly_add_component — вставка детали из .m3d в сборку (API7)

Веха СБОРКИ, инкремент 1. Новый AssemblyService (API7, паттерн HoleService):
AddFromFile → Placement.SetOrigin → UpdatePlacement. Сигнатуры подтверждены
рефлексией interop. Валидация — чистые static. Тест — габаритом/числом компонентов.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 12:50:35 +03:00
parent 560845c448
commit a8f7363869
@@ -0,0 +1,116 @@
# Дизайн: вставка компонента в сборку (assembly_add_component) через API7
**Дата:** 2026-05-27
**Статус:** спроектировано (сигнатуры подтверждены рефлексией interop + kompas-sdk-research); на ревью (Codex)
## Цель
Открыть новый класс функционала — **СБОРКИ**. До сих пор сделана только геометрия ДЕТАЛИ.
Первый инкремент вехи: **вставить деталь из сохранённого `.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)`.
```
IKompasDocument3D doc3d = (IKompasDocument3D)app.ActiveDocument; // должна быть СБОРКА
// валидация: ((IKompasDocument)doc3d).DocumentType == ksDocumentAssembly
IPart7 top = (IPart7)doc3d.TopPart;
IParts7 parts = top.Parts; // COM-свойство
IPart7 part = (IPart7)parts.AddFromFile(filePath, /*ExternalFile*/ true, /*Redraw*/ true);
// ExternalFile=true: компонент — ссылка на внешний .m3d (что нам и нужно — отдельный файл-источник).
// Redraw=true: перестроить сразу (отдельный RebuildModel не нужен).
IPlacement3D pl = (IPlacement3D)part.Placement; // read-only свойство, мутируем
pl.SetOrigin(x, y, z); // мировые координаты сборки, мм
part.UpdatePlacement(/*Redraw*/ true); // зафиксировать перемещение
```
**Почему `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`:
- `RequireComponentPath(string? path)` — путь непустой (формат строки).
- `RequireFinitePosition(double x, double y, double z)` — координаты конечны (`double.IsFinite`;
отсекает `Infinity`/`NaN`, как в `HoleService`).
Проверка существования файла (`File.Exists`) — в сервисе (не чистая, не unit-тест), даёт
`FileNotFoundException` с путём.
## Реализация
- `AssemblyService.AddComponentAsync(string filePath, double x, double y, double z, ct)`
на STA-потоке: валидация → проверка типа активного документа (сборка) → `AddFromFile`
`Placement.SetOrigin``UpdatePlacement` → вернуть `AddedComponent(Index, Name)`.
Индекс = `parts.Count - 1` после вставки; имя = `part.Name`.
- Транзитные RCW не освобождаем точечно — консистентно с остальным слоем (долг v2-2).
## Тестирование
### Unit (`AssemblyValidationTests`)
- `RequireComponentPath` бросает на null/пустой/пробельный; пропускает валидный.
- `RequireFinitePosition` бросает на NaN/Infinity по каждой координате; пропускает конечные.
### Integration (`AssemblyTests`, Collection KompasCollection)
Паттерн: построить деталь → `SaveAsAsync(.scratch/*.m3d)``CloseAsync` → создать сборку →
`AddComponentAsync` → проверить → `finally CloseAsync`.
1. **Вставка в начало координат:** деталь-коробка 40×40×20 (эскиз 0..40 × 0..40, выдавить 20),
сохранить, закрыть. Создать сборку, `AddComponentAsync(file, 0,0,0)`.
- `list_components``Count == 1`; компонент: `SizeX≈40, SizeY≈40, SizeZ≈20` (±допуск).
- `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 подтверждает работу позиции).
Объёмом сборку тоже можно проверить (`get_part_info` на TopPart агрегирует компоненты) —
но габарит надёжнее и прямо проверяет позицию. Артефакты `.m3d` — в gitignored `.scratch/`.
## Дальнейшие инкременты (вне этого спека)
2. `assembly_add_mate` — сопряжения (`IMateConstraints3D.Add(mc_Coincidence/mc_Distance/…)` +
`BaseObject1/2` + `Update`). Типы: совпадение/параллельность/перпендикулярность/касательность/
концентричность/расстояние/угол/симметрия.
3. Перемещение/массив/фиксация компонентов — по необходимости.