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

Веха СБОРКИ, инкремент 1. Новый AssemblyService (API7, паттерн HoleService):
IParts7.AddFromFile → Placement.SetOrigin → RebuildModel. Новый AssemblyTools
(MCP-инструмент assembly_add_component), регистрация в DI.

Ключевая находка: UpdatePlacement возвращает FALSE для вручную позиционируемого
компонента (не ошибка), позицию применяет SetOrigin + RebuildModel(true).

Валидация — чистый AssemblyValidation (расширение .m3d/.a3d, нормализация пути,
конечность координат). RequireActiveAssembly — раздельные ошибки по типу документа.

Тесты: +14 unit (AssemblyValidation), +3 integration (AssemblyTests: вставка в
origin, позиционирование со сдвигом, отклонение не-сборки). Итого 83 unit + 53
integration = 136 зелёных.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:04:19 +03:00
parent 5f17884f06
commit 5eee7ad1af
8 changed files with 343 additions and 1 deletions
@@ -1,7 +1,19 @@
# Дизайн: вставка компонента в сборку (assembly_add_component) через API7
**Дата:** 2026-05-27
**Статус:** спроектировано (сигнатуры подтверждены рефлексией interop + kompas-sdk-research); ревью Codex учтено (8 замечаний внесены)
**Статус:** реализовано и проверено (3 интеграционных теста зелёные, 136 всего); ревью Codex спека учтено (8 замечаний)
## Эмпирическая находка при реализации (важно — не разучивать)
`part.UpdatePlacement(true)` возвращает **FALSE** при перемещении вручную позиционируемого
компонента (без сопряжений) — это **НЕ ошибка** (нечего пересчитывать от зависимостей). Само
перемещение задаёт `placement.SetOrigin(x,y,z)` (живой объект Placement, не копия — проверено:
сдвиг origin на +50 даёт габарит сборки X[50,90]), а **видимым в геометрии** оно становится после
`top.RebuildModel(true)`. Поэтому: результат `SetOrigin` проверяем (FALSE = ошибка), результат
`UpdatePlacement` **игнорируем**, обязателен `RebuildModel(true)`. Разфиксация первого компонента
(`Fixed=false`) **НЕ нужна** — зафиксированный КОМПАСом первый компонент всё равно перемещается
через `SetOrigin`+`RebuildModel` (проверено: оба теста — origin и offset — зелёные без снятия
фиксации).
## Цель