Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs
T
mikhail 8afad88241 test: per-test очистка документов через IntegrationTestBase (CloseAllAsync)
Базовый класс IntegrationTestBase (IAsyncLifetime) закрывает все открытые
документы КОМПАС после КАЖДОГО теста (DocumentService.CloseAllAsync) — чтобы не
копились вкладки-пустышки и упавший Assert не оставлял открытый документ. Все
интеграционные тест-классы переведены на него (: base(fx)); индивидуальные
finally CloseAsync сохранены как первичная очистка.

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

175 lines
8.7 KiB
C#

using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: операция «Отверстие» (API7 IHoles3D/IHole3D).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class HoleTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly HoleService _holes;
private readonly QueryService _query;
public HoleTests(KompasFixture fx) : base(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); }
}
[Fact]
public async Task Counterbore_removes_base_plus_spotface()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
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;
// Ø10 сквозное + цековка Ø20×5 по центру верхней грани.
await _holes.CounterboreHoleAsync(0, 0, 20, diameter: 10, depth: 0, throughAll: true,
spotDiameter: 20, spotDepth: 5);
var removed = before - (await _query.GetPartInfoAsync()).Volume;
// База: π·25·20 ≈ 1570.8; + кольцо цековки (π·100−π·25)·5 ≈ 1178 → итого ≈ 2749.
var baseCyl = Math.PI * 25 * 20;
var expected = Math.PI * 25 * 20 + Math.PI * (100 - 25) * 5;
Assert.True(removed > baseCyl * 1.1, "Цековка должна удалить больше, чем простое отверстие.");
Assert.InRange(removed, expected * 0.9, expected * 1.1);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Countersink_removes_base_plus_cone()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
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;
// Ø8 сквозное + зенковка Ø16 под 90° по центру верхней грани.
await _holes.CountersinkHoleAsync(0, 0, 20, diameter: 8, depth: 0, throughAll: true,
sinkDiameter: 16, sinkAngle: 90);
var removed = before - (await _query.GetPartInfoAsync()).Volume;
// База Ø8 сквозь 20 ≈ 1005; зенковка-конус добавляет ~270 (точное зависит от трактовки угла).
var baseCyl = Math.PI * 16 * 20;
Assert.True(removed > baseCyl, "Зенковка должна удалить больше, чем простое отверстие Ø8.");
Assert.True(removed < baseCyl * 2, "Объём зенковки в разумных пределах.");
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Conic_hole_removes_tapered_volume()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
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;
// Коническое Ø10 у дна, угол 20°, глухое на 15 мм по центру верхней грани.
await _holes.ConicHoleAsync(0, 0, 20, diameter: 10, depth: 15, throughAll: false, conicAngle: 20);
var removed = before - (await _query.GetPartInfoAsync()).Volume;
// Конус расширяется к поверхности → объём больше цилиндра Ø10×15 (π·25·15≈1178) и в разумных пределах.
var baseCyl = Math.PI * 25 * 15;
Assert.True(removed > baseCyl, "Конусное отверстие должно удалить больше цилиндра Ø10.");
Assert.True(removed < baseCyl * 4, "Объём конуса в разумных пределах.");
}
finally { await _docs.CloseAsync(save: false); }
}
}