# Дизайн: сопряжение компонентов сборки (assembly_add_mate) через API7

**Дата:** 2026-05-27
**Статус:** реализовано и проверено (2 интеграционных + 14 unit, всего 154); ревью Codex спека (8) и реализации (9) учтено

## Правки по ревью реализации Codex (9 замечаний)

- **#1 `Valid==false` → ошибка** (а не «успех с valid=false»): после Update+Rebuild проверяем
  `mate.Valid`, иначе бросаем (срабатывает откат). `AddedMate.Valid` теперь всегда true при успехе.
  Это же косвенно ловит грани одного компонента (#5) — мат выродится.
- **#6/#7 distance `value` > 0** (не ≥0): нулевой зазор вырождается в coincidence →
  `RequireMateValue` требует строго `> 0`; unit-кейс `value=0` бросает.
- **#8 негативный интеграционный тест**: точка в пустоте → грань не найдена → ошибка.
- **#2 откат best-effort** оставлен намеренно (исходное исключение важнее ошибки отката; паттерн
  `HoleService`). **#3** `mates.Add ?? throw` до try корректно (null = ничего не добавлено).
  **#4** «первый IFace» оставлен (консистентно с HoleService/move_face). **#9** точечный release
  RCW отклонён (конвенция v2-2).

## Решения по ревью Codex (важные правки к дизайну ниже)

- **Scope урезан до `coincidence` + `distance`** (#6, #5): только эти два типа геометрически
  подтверждены спайком → только они в публичном `MateType`/`Parse`. Остальные (parallel/
  perpendicular/concentric/angle/tangency) и `Alignment`-параметр — будущие инкременты с тестами.
- **Валидация value по типу** (#1): `Mates.RequiresValue` → distance требует `value` конечный и
  `≥ 0`; для типов без value параметр игнорируется (дефолт 0). Валидатор
  `AssemblyValidation.RequireMateValue(requiresValue, value)`.
- **Полный откат** (#3): весь участок после `mates.Add` — в try/catch; при ЛЮБОЙ ошибке (BaseObject,
  ParamValue, Update=FALSE, RebuildModel=FALSE) сопряжение удаляется через `mate.Owner?.Delete()`
  (`IFeature7`, как у компонента в инкременте 1).
- **Возврат результата** (#8b): `AddedMate { Type: string, Valid: bool }` — для отчёта инструмента и
  ассертов теста (`Valid`).
- **Усиленный тест** (#2): явно `bbBefore ≈ 120`; после mate — `Valid==true`, размеры каждого
  компонента (`SizeX≈20`) неизменны, ширина сборки изменилась.
- **RCW** (#7): точечный release НЕ вводим (конвенция v2-2, как в инкременте 1/HoleService); все
  COM-вызовы — внутри `dispatcher.InvokeAsync` (уже так). Долг зафиксирован в OPEN_QUESTIONS v2-2.
- **Выбор грани** (#8a): паттерн «первый IFace» (как в HoleService/move_face); в описании
  инструмента — «точка в СЕРЕДИНЕ грани» (на ребре/углу хит неоднозначен).

## Цель

Веха СБОРКИ, инкремент 2. После вставки компонентов (`assembly_add_component`, инкремент 1) —
**сопряжения**: наложить связь между гранями двух компонентов (совпадение / на расстоянии),
чтобы решатель сборки спозиционировал компоненты относительно друг друга. Это вторая
половина минимально полезного цикла сборки (вставить → связать).

## Спайк: механизм подтверждён вживую (НЕ разучивать)

Спайк (`AssemblyMateSpikeTests`, оба теста зелёные) подтвердил полный путь API7:

```
IPart7 top = (IPart7)doc3d.TopPart;                       // активная сборка
IMateConstraints3D mates = top.MateConstraints;           // COM-свойство (не QI)
IMateConstraint3D mate = mates.Add(MateConstraintType);   // напр. mc_Coincidence=0 / mc_Distance=5
mate.BaseObject1 = (IModelObject)face1;                   // грань компонента 1
mate.BaseObject2 = (IModelObject)face2;                   // грань компонента 2
if (distance|angle) mate.ParamValue = value;              // зазор (мм) / угол (°)
bool ok = mate.Update();                                  // наложить сопряжение
top.RebuildModel(true);                                   // применить к геометрии
```

**Выбор граней компонентов:** `top.FindObjectsByPoint(x, y, z, FirstLevel=false)` — `FirstLevel=false`
заставляет спуститься В компоненты и вернуть их грань в **контексте сборки** (мировые координаты с
учётом placement компонента). Это именно тот `IModelObject`, который нужен решателю сопряжений.

**Проверенные результаты (2 куба 20³, A в нуле x[0,20], B со сдвигом +100 x[100,120]):**
- **Совпадение** (`mc_Coincidence`) +X грани A (20,10,10) с −X гранью B (100,10,10): `Update=True`,
  `Valid=True`, `Alignment=ksMCAlignmentClosest` (дефолт). Грани сошлись заподлицо → ширина сборки
  по X **120 → 40** (кубы соприкасаются). Решатель подвинул A (к B), не B.
- **Расстояние** (`mc_Distance`, `ParamValue=30`) тех же граней: `Update=True`, `Valid=True`. Грани
  встали в 30 мм → ширина сборки по X **120 → 70** (20+30+20).

**Тонкости (эмпирика):**
- `Alignment` НЕ выставляем — дефолт работает (`ksMCAlignmentClosest` для совпадения,
  `ksMCAlignmentUnknown` для расстояния). Параметр выравнивания — расширение позже (YAGNI).
- Решатель может двигать **любой** компонент (в т.ч. первый, «зафиксированный»). Поэтому результат
  проверяем по **ширине габарита** (грани сблизились), а не по тому, какой компонент сдвинулся.
- `mates.Add` возвращает `IMateConstraint3D`; результат `Update()` проверяем (FALSE = не наложилось).
  После — `RebuildModel(true)` (результат проверяем, как в инкременте 1).

## Архитектура

Метод в существующем `AssemblyService` (он уже про сборки). Свой enum `MateType` (паттерн
`BasePlane`/`CoordinateAxis`) маппится на КОМПАС `MateConstraintType` — COM-константы не текут в
Host/тесты.

- `src/Kompas.Mcp.Core/Assemblies/MateType.cs` — enum `MateType` + static `Mates`
  (`ToConstraintType`, `Parse(string)` англ./рус., `RequiresValue`).
- `AssemblyService.AddMateAsync(MateType, x1,y1,z1, x2,y2,z2, value, ct)` — реализация.
- Инструмент `assembly_add_mate` в `AssemblyTools` (тип сопряжения строкой → `Mates.Parse`).

## MCP-инструмент

| Инструмент | Параметры | Поведение |
|---|---|---|
| `assembly_add_mate` | `mateType: string`, `x1,y1,z1`, `x2,y2,z2`, `value=0` | Наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам (x1..z1) и (x2..z2). `mateType`: coincidence / distance / parallel / perpendicular / concentric / angle / tangency. Для distance — `value` зазор (мм), для angle — `value` угол (°). |

Контракт: активный документ — сборка (`RequireActiveAssembly`, уже есть); обе точки должны попасть
на грани компонентов. **Гарантированно валидированы геометрией: coincidence, distance.** Остальные
типы маппятся и вызываемы через тот же путь Add+BaseObject+Update, но не покрыты регрессионным
тестом по габариту (требуют иной геометрии/проверки) — помечены в описании инструмента.

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

`Mates`:
- `Parse(string)` — англ./рус. → `MateType`; иначе `ArgumentException` со списком допустимых.
- `ToConstraintType(MateType)` — → `MateConstraintType`.
- `RequiresValue(MateType)` — true для Distance/Angle.

В сервисе: `RequireFinitePosition` для обеих точек (уже есть в `AssemblyValidation`); если
`RequiresValue` — проверить `double.IsFinite(value)` (новый чистый валидатор
`AssemblyValidation.RequireFiniteValue`).

## Реализация

`AddMateCore`: валидация точек (и value, если нужен) → `RequireActiveAssembly` →
`FindFaceInAssembly` (FirstLevel=false) для обеих точек (понятная ошибка, если грань не найдена) →
`mates.Add(Mates.ToConstraintType(type))` → `BaseObject1/2` → `ParamValue` (если нужен) →
`Update()` (FALSE → откат сопряжения через `IFeature7.Delete` + ошибка) → `RebuildModel(true)`
(FALSE → ошибка). Транзитные RCW не освобождаем точечно (долг v2-2, как везде).

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

### Unit (`MateTests` или в `AssemblyValidationTests`)
- `Mates.Parse` — англ. и рус. синонимы → верный `MateType`; бросает на мусоре.
- `Mates.ToConstraintType` — каждый `MateType` → ожидаемый `MateConstraintType`.
- `Mates.RequiresValue` — true для Distance/Angle, false для прочих.
- `AssemblyValidation.RequireFiniteValue` — бросает на NaN/Infinity.

### Integration (`AssemblyMateTests`)
Паттерн: построить куб 20³ → сохранить → создать сборку → вставить 2 компонента (A в нуле, B +100)
→ сопряжение → проверить ширину габарита → `finally CloseAsync`.
1. **Совпадение** граней A(+X)/B(−X): ширина по X **120 → ~40** (соприкосновение).
2. **Расстояние 30**: ширина по X **120 → ~70** (зазор 30).
(Эти два сценария = промотированный спайк; спайк-файл удаляется.)

## Дальнейшее (вне спека)
- Выравнивание (`Alignment`), типы concentric/angle/parallel с геометрической проверкой.
- Перемещение/фиксация/массив компонентов.
