- направление сверления — по факту убыли объёма (Update=TRUE даже при сверлении в воздух на сквозном отверстии); TryDirection + CurrentVolume (API5 МЦХ) — P2-баг, вскрыт тестом - валидация double.IsFinite для координат/диаметра/глубины (Infinity проходил мимо >0) — P2 - откат точки-сироты и объекта отверстия при неуспехе (IFeature7.Delete) + RebuildDocument — P2 - тест Hole_through_bottom_face_uses_direction_fallback (нижняя грань) — P3 - 104 теста зелёных
7.2 KiB
Дизайн: отверстие (hole) через API7
Дата: 2026-05-27 Статус: реализовано и проверено (spike зелёный); на ревью (ревьюер — Codex)
Цель
Добавить параметрическую операцию «Отверстие» (простое цилиндрическое, ksHTBase) — сквозное или глухое, размещённое на грани по мировой точке. Приоритет 2 из плана.
Ключевой факт: отверстие — только API7
В API5 определения отверстия НЕТ (по рефлексии Kompas6API5.dll нет ни одного типа с Hole;
есть лишь Obj3dType.o3d_holeOperation=52 без интерфейса — как было с draft). В API7 —
богатая поддержка: IModelContainer.Holes3D (IHoles3D) → Add() → IHole3D, размещение через
IHoleDisposal. Прецедент API7 в слое моделирования уже есть — move_face (FaceEditService).
Поэтому отдельный сервис HoleService (API7), а не метод PartModeler (API5).
Подтверждённый workflow (spike зелёный)
IPart7 top = (IPart7)doc3d.TopPart;
IFace face = top.FindObjectsByPoint(x,y,z,true) → первый IFace; // как в move_face
IModelContainer c = (IModelContainer)top; // COM-QI
// Точка размещения центра отверстия в МИРОВЫХ координатах:
IPoint3D pt = c.Points3D.Add();
pt.ParameterType = ksPParamCoord; pt.X=x; pt.Y=y; pt.Z=z; pt.Update();
IHole3D hole = c.Holes3D.Add();
hole.HoleType = ksHTBase; // простое цилиндрическое
hole.Diameter = diameter; // мм
hole.DepthType = throughAll ? ksDTReachThrough : ksDTValue;
if (!throughAll) hole.Depth = depth; // мм
hole.EndFaceType = ksEFFlat; hole.Axis=false; hole.ShowThread=false;
IHoleDisposal d = (IHoleDisposal)hole; // QI
d.BaseSurface = (IModelObject)face; // обязательна грань
d.Perpendicular = true;
d.AssociationVertex = (IModelObject)pt; // центр = созданная точка
d.Direction = true; // подбор по факту (см. ниже)
hole.Update(); doc3d.RebuildDocument(); // проверяем убыль объёма; иначе Direction=false
Направление сверления (важно): нормаль грани ориентирована непредсказуемо (тот же класс
проблем, что у boss/cut в OPEN_QUESTIONS), а при сквозном отверстии Update() возвращает
TRUE даже когда сверлит «в воздух» (наружу тела → удалено 0 материала). Поэтому направление
подбираем по факту удаления материала: строим с Direction=true, перестраиваем, сравниваем
объём (API5 CalcMassInertiaProperties, согласован с API7-геометрией после RebuildDocument);
если объём не убыл — Direction=false и повтор; если оба не убавили — ошибка. Только успех
Update недостаточен.
Размещение (главная неочевидность, решена): у IHole3D нет свойства позиции — оно в
IHoleDisposal (BaseSurface = грань + AssociationVertex = точка/вершина). Самый надёжный
способ задать центр по мировым координатам — создать IPoint3D (ksPParamCoord + X/Y/Z) и
передать его как AssociationVertex. Эскиз размещения НЕ требуется (в отличие от extrude_cut).
Единицы: Diameter/Depth/X/Y/Z — мм. EndFaceType=ksEFFlat (плоский торец) для глухого.
MCP-инструмент (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
hole |
x,y,z: double, diameter: double, depth=0, throughAll=false |
Просверлить цилиндрическое отверстие на грани, найденной по точке (x,y,z — центр). throughAll — сквозное (depth не нужен), иначе глухое на depth. Направление в тело — авто (как move_face). |
Решения (YAGNI): на первом этапе только ksHTBase (простое цилиндрическое); зенковка/
цековка/коническое (ksHTCounterbore/Countersinking/Conic + параметрические подынтерфейсы) и
резьба (ShowThread/IThread) — расширения позже. Глубина «до объекта» (ksDTObject) не
выставляется. Отверстие не регистрируется в реестре _features (он для API5-операций) — массивы/
зеркало пакета C к нему неприменимы; контракт инструмента возвращает подтверждение без id.
Реализация
src/Kompas.Mcp.Core/Modeling/HoleService.cs(новый):HoleAsync(x,y,z,diameter,depth,throughAll,ct)на STA-потоке; валидацияdouble.IsFiniteдля координат/диаметра/глубины +>0;FindFaceAtPoint(копия паттерна изFaceEditService); подбор направления по убыли объёма (TryDirection); при неуспехе — откат точки и объекта отверстия (IFeature7.Delete), затемRebuildDocument.- DI:
AddSingleton<HoleService>()вProgram.cs. - Инструмент
holeвFeatureTools.cs(конструктор получаетHoleService). - Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
Тестирование (Integration, HoleTests)
- Сквозное: коробка 40×40×20 (центр 0,0; верх Z=20) →
hole(0,0,20, Ø10, throughAll)→ удалён цилиндр π·25·20 ≈ 1570.8 мм³;InRange(before-after, ±5%). ✓ - Глухое: та же коробка →
hole(0,0,20, Ø8, depth=10)→ удалён π·16·10 ≈ 502.7 мм³;InRange(before-after, ±10%)(торец плоский, погрешность модели). ✓ - Нижняя грань (подбор направления): та же коробка →
hole(0,0,0, Ø10, throughAll)со стороны нижней грани → корректное направление внутрь (+Z) подбирается по убыли объёма; удалён π·25·20. ✓