fix: правки hole по ревью Codex

- направление сверления — по факту убыли объёма (Update=TRUE даже при сверлении в воздух
  на сквозном отверстии); TryDirection + CurrentVolume (API5 МЦХ) — P2-баг, вскрыт тестом
- валидация double.IsFinite для координат/диаметра/глубины (Infinity проходил мимо >0) — P2
- откат точки-сироты и объекта отверстия при неуспехе (IFeature7.Delete) + RebuildDocument — P2
- тест Hole_through_bottom_face_uses_direction_fallback (нижняя грань) — P3
- 104 теста зелёных
This commit is contained in:
2026-05-27 10:04:22 +03:00
parent 13ab0f6581
commit d3dd78ec0b
3 changed files with 105 additions and 27 deletions
@@ -38,11 +38,18 @@ IHoleDisposal d = (IHoleDisposal)hole; // QI
d.BaseSurface = (IModelObject)face; // обязательна грань
d.Perpendicular = true;
d.AssociationVertex = (IModelObject)pt; // центр = созданная точка
d.Direction = true; // в тело; при FALSE — пробуем обратное
hole.Update(); // повтор с Direction=false при отказе
doc3d.RebuildDocument();
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) и
@@ -65,8 +72,9 @@ doc3d.RebuildDocument();
## Реализация
- **`src/Kompas.Mcp.Core/Modeling/HoleService.cs`** (новый): `HoleAsync(x,y,z,diameter,depth,throughAll,ct)`
на STA-потоке; валидация `diameter>0`, `depth>0` (если не throughAll); `FindFaceAtPoint`
(копия паттерна из `FaceEditService`); авто-направление с повтором.
на STA-потоке; валидация `double.IsFinite` для координат/диаметра/глубины + `>0`; `FindFaceAtPoint`
(копия паттерна из `FaceEditService`); подбор направления по убыли объёма (`TryDirection`);
при неуспехе — откат точки и объекта отверстия (`IFeature7.Delete`), затем `RebuildDocument`.
- **DI:** `AddSingleton<HoleService>()` в `Program.cs`.
- **Инструмент** `hole` в `FeatureTools.cs` (конструктор получает `HoleService`).
- Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
@@ -77,3 +85,5 @@ doc3d.RebuildDocument();
удалён цилиндр π·25·20 ≈ 1570.8 мм³; `InRange(before-after, ±5%)`. ✓
2. **Глухое**: та же коробка → `hole(0,0,20, Ø8, depth=10)` → удалён π·16·10 ≈ 502.7 мм³;
`InRange(before-after, ±10%)` (торец плоский, погрешность модели). ✓
3. **Нижняя грань (подбор направления)**: та же коробка → `hole(0,0,0, Ø10, throughAll)` со стороны
нижней грани → корректное направление внутрь (+Z) подбирается по убыли объёма; удалён π·25·20. ✓