Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/Integration/FilletByPointsTests.cs
T
mikhail 461e731857 Дерево построения: имя у каждой операции, скругление пакетом по точкам
Имя (name) прокидывается до создания объекта: API5 — ksEntity.name перед Create(),
API7-пути (примитив, отверстие, булева, рез, перенос грани) — IModelObject.Name перед
Update(). Читается назад через ksFeature.name, который только на чтение. Без имени
дерево состоит из «Эскиз:1» и «Элемент выдавливания:3».

fillet_edge/chamfer_edge принимают список точек: на теле с сотнями рёбер list_edges
неподъёмен, а выбор по точке брал одно ребро — четыре угла давали четыре операции.
Ненайденные рёбра перечисляются ВСЕ сразу, модель при этом не мутируется.
2026-07-31 16:35:44 +03:00

89 lines
4.1 KiB
C#

using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>
/// Интеграция: скругление пакетом по ТОЧКАМ. На теле с сотнями рёбер (надпись!) список из
/// list_edges неподъёмен по объёму ответа, а выбор по точке раньше брал ровно одно ребро —
/// четыре угла давали четыре операции в дереве. Теперь это одна операция.
/// </summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class FilletByPointsTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly ModelInspectionService _inspection;
private readonly QueryService _query;
public FilletByPointsTests(KompasFixture fx) : base(fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
_inspection = new ModelInspectionService(fx.Session, fx.Dispatcher);
_query = new QueryService(fx.Session, fx.Dispatcher);
}
/// <summary>Плита 20×10×3 от начала координат: вертикальные рёбра — в её четырёх углах.</summary>
private async Task PlateAsync()
{
await _docs.CreateAsync(KompasDocumentType.Part);
var s = await _modeler.OpenSketchAsync(BasePlane.XOY, name: "Контур плиты");
await _modeler.AddRectangleAsync(s, 0, 0, 20, 10);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 3, name: "Плита");
await _modeler.RebuildAsync();
}
[Fact]
public async Task Four_corners_become_one_fillet_in_the_tree()
{
await PlateAsync();
try
{
var before = (await _query.GetPartInfoAsync()).Volume;
await _modeler.FilletEdgesByPointsAsync(
[
new Point3d(0, 0, 1.5), new Point3d(20, 0, 1.5),
new Point3d(20, 10, 1.5), new Point3d(0, 10, 1.5),
], radius: 2, name: "Скругления углов плиты");
await _modeler.RebuildAsync();
var tree = (await _inspection.DescribeModelAsync(InspectionText.ModelSections.Tree)).Features;
Assert.Single(tree, f => f.Name == "Скругления углов плиты");
// Каждый угол теряет (4 − π)·r²/4 · h материала — проверяем, что скруглены все четыре.
var expected = before - 4 * (4 - Math.PI) / 4 * 2 * 2 * 3;
Assert.InRange((await _query.GetPartInfoAsync()).Volume, expected * 0.99, expected * 1.01);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Points_without_an_edge_are_all_listed_at_once()
{
await PlateAsync();
try
{
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _modeler.FilletEdgesByPointsAsync(
[
new Point3d(0, 0, 1.5), // ребро есть
new Point3d(100, 100, 100), // пусто
new Point3d(-50, -50, -50), // пусто
], radius: 1));
// Обе промахнувшиеся точки названы в ОДНОМ сообщении: иначе агент выясняет их по одной.
Assert.Contains("[1]", ex.Message, StringComparison.Ordinal);
Assert.Contains("[2]", ex.Message, StringComparison.Ordinal);
// Модель не тронута — операция не создавалась.
var tree = (await _inspection.DescribeModelAsync(InspectionText.ModelSections.Tree)).Features;
Assert.DoesNotContain(tree, f => f.Name.Contains("Скругление", StringComparison.OrdinalIgnoreCase));
}
finally { await _docs.CloseAsync(save: false); }
}
}