using Kompas.Mcp.Core.Documents; using Kompas.Mcp.Core.Modeling; using Kompas.Mcp.Core.Query; namespace Kompas.Mcp.Tests.Integration; /// Интеграция: операция «Отверстие» (API7 IHoles3D/IHole3D). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] public sealed class HoleTests { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly HoleService _holes; private readonly QueryService _query; public HoleTests(KompasFixture fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); _holes = new HoleService(fx.Session, fx.Dispatcher); _query = new QueryService(fx.Session, fx.Dispatcher); } [Fact] public async Task Hole_through_top_face_removes_cylinder_volume() { await _docs.CreateAsync(KompasDocumentType.Part); try { // Коробка 40×40×20 (центр в начале координат), верхняя грань на Z=20. var s = await _modeler.OpenSketchAsync(BasePlane.XOY); await _modeler.AddRectangleAsync(s, -20, -20, 20, 20); await _modeler.CloseSketchAsync(s); await _modeler.ExtrudeAsync(s, depth: 20); var before = (await _query.GetPartInfoAsync()).Volume; // 32000 // Сквозное отверстие Ø10 по центру верхней грани (0,0,20). await _holes.HoleAsync(0, 0, 20, diameter: 10, depth: 0, throughAll: true); var after = (await _query.GetPartInfoAsync()).Volume; // Удалён цилиндр π·25·20 ≈ 1570.8 мм³. var removed = Math.PI * 25 * 20; Assert.True(after < before, "После отверстия объём должен уменьшиться."); Assert.InRange(before - after, removed * 0.95, removed * 1.05); } finally { await _docs.CloseAsync(save: false); } } [Fact] public async Task Hole_blind_in_top_face_removes_partial_cylinder() { await _docs.CreateAsync(KompasDocumentType.Part); try { // Коробка 40×40×20, верхняя грань на Z=20. var s = await _modeler.OpenSketchAsync(BasePlane.XOY); await _modeler.AddRectangleAsync(s, -20, -20, 20, 20); await _modeler.CloseSketchAsync(s); await _modeler.ExtrudeAsync(s, depth: 20); var before = (await _query.GetPartInfoAsync()).Volume; // 32000 // Глухое отверстие Ø8 глубиной 10 по центру верхней грани. await _holes.HoleAsync(0, 0, 20, diameter: 8, depth: 10, throughAll: false); var after = (await _query.GetPartInfoAsync()).Volume; // Удалён цилиндр π·16·10 ≈ 502.7 мм³ (без учёта плоского торца — EndFaceType=Flat). var removed = Math.PI * 16 * 10; Assert.True(after < before, "После глухого отверстия объём должен уменьшиться."); Assert.InRange(before - after, removed * 0.9, removed * 1.1); } finally { await _docs.CloseAsync(save: false); } } [Fact] public async Task Hole_through_bottom_face_uses_direction_fallback() { await _docs.CreateAsync(KompasDocumentType.Part); try { // Коробка 40×40×20, нижняя грань на Z=0 (нормаль смотрит наружу, −Z). var s = await _modeler.OpenSketchAsync(BasePlane.XOY); await _modeler.AddRectangleAsync(s, -20, -20, 20, 20); await _modeler.CloseSketchAsync(s); await _modeler.ExtrudeAsync(s, depth: 20); var before = (await _query.GetPartInfoAsync()).Volume; // 32000 // Сверлим со стороны НИЖНЕЙ грани (0,0,0): корректное направление в тело — внутрь (+Z), // т.е. противоположно нормали → проверяет авто-подбор направления (fallback). await _holes.HoleAsync(0, 0, 0, diameter: 10, depth: 0, throughAll: true); var after = (await _query.GetPartInfoAsync()).Volume; var removed = Math.PI * 25 * 20; Assert.True(after < before, "Отверстие с нижней грани должно построиться (fallback направления)."); Assert.InRange(before - after, removed * 0.95, removed * 1.05); } finally { await _docs.CloseAsync(save: false); } } }