Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md
mikhail 3fe06f61dc feat: assembly_add_mate — упрочнение по ревью Codex реализации
#1: Valid==false → ошибка (откат), а не «успех с valid=false»; косвенно отсекает
грани одного компонента (вырожденный мат). #6/#7: distance value строго > 0
(нулевой зазор = coincidence) + unit-кейс value=0 бросает. #8: негативный
интеграционный тест — точка в пустоте → грань не найдена → ошибка.
#2/#3/#4 оставлены по обоснованию (best-effort откол, Add??throw, первый IFace —
консистентно с HoleService). #9 точечный release RCW отклонён (конвенция v2-2).

Тесты: 98 unit + 58 integration = 156 зелёных.

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

12 KiB
Raw Permalink Blame History

Дизайн: сопряжение компонентов сборки (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, если нужен) → RequireActiveAssemblyFindFaceInAssembly (FirstLevel=false) для обеих точек (понятная ошибка, если грань не найдена) → mates.Add(Mates.ToConstraintType(type))BaseObject1/2ParamValue (если нужен) → 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 с геометрической проверкой.
  • Перемещение/фиксация/массив компонентов.