Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/Integration/MergedToolPathsTests.cs
T
mikhail a4cc755fb4 Формообразование: надписи в эскизе, тонкая стенка, тела по размерам
Три способа получить форму там, где раньше был только замкнутый контур
из отрезков и дуг.

1. Примитив эскиза type=text: ksTextEx + ksConvertTextToCurve. Пока текст
   остаётся текстом, это оформление, и операция его не видит; после
   конвертации глифы становятся обычным сечением. Ответ возвращает
   фактическую длину строки — иначе ширину шрифта до выдавливания не узнать.

2. extrude(thinThickness, thinSide): контур трактуется как стенка заданной
   толщины. outward по замкнутому контуру даёт кайму-эквидистанту вокруг
   него — так надпись получает подложку без 2D-эквидистанты.
   Толщину при dtReverse КОМПАС читает из reverseThickness: положить её в
   normalThickness значит молча получить СПЛОШНОЕ сечение вместо рамки.

3. Инструмент primitive: элементарные тела API7 (block, cylinder, sphere,
   cone) с result=new|union|subtract|intersect. Карман и паз строятся
   вычитанием тела, без эскиза и выреза. Update() возвращает TRUE даже
   когда вычитание прошло мимо тела, поэтому сервис сверяет объём до и
   после и откатывает операцию, если ничего не изменилось.
2026-07-31 14:50:56 +03:00

181 lines
7.8 KiB
C#

using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>
/// Интеграция: механики, на которых держатся слитые инструменты — пакет примитивов эскиза
/// за один заход, мост «индекс грани → точка на ней», скругление/фаска пачкой рёбер и upsert
/// переменной. Каждая цепочка проверяется на живом КОМПАС: слияние инструментов не должно
/// менять результат построения.
/// </summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class MergedToolPathsTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly QueryService _query;
private readonly ModelInspectionService _inspection;
private readonly VariableService _variables;
public MergedToolPathsTests(KompasFixture fx) : base(fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
_query = new QueryService(fx.Session, fx.Dispatcher);
_inspection = new ModelInspectionService(fx.Session, fx.Dispatcher);
_variables = new VariableService(fx.Session, fx.Dispatcher);
}
private static SketchEntity Rect(double x1, double y1, double x2, double y2) => new()
{
Kind = SketchEntityKind.Rectangle,
Points = [(x1, y1), (x2, y2)],
};
private static SketchEntity Circle(double cx, double cy, double r) => new()
{
Kind = SketchEntityKind.Circle,
CenterX = cx, CenterY = cy, Radius = r,
};
[Fact]
public async Task Batch_builds_the_whole_contour_in_one_call()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Плита 40×20 с четырьмя отверстиями Ø4 — раньше это было 6 вызовов, теперь один.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
var added = await _modeler.AddEntitiesAsync(s,
[
Rect(0, 0, 40, 20),
Circle(5, 5, 2), Circle(35, 5, 2), Circle(5, 15, 2), Circle(35, 15, 2),
], autoClose: true);
Assert.Equal(5, added.Count);
await _modeler.ExtrudeAsync(s, depth: 10);
await _modeler.RebuildAsync();
// Объём плиты минус четыре цилиндра.
var expected = (40 * 20 - 4 * Math.PI * 4) * 10;
var v = (await _query.GetPartInfoAsync()).Volume;
Assert.InRange(v, expected * 0.98, expected * 1.02);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Batch_reports_the_failing_position()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
var bad = new SketchEntity { Kind = SketchEntityKind.Circle, CenterX = 0, CenterY = 0, Radius = -1 };
var ex = await Assert.ThrowsAnyAsync<Exception>(
() => _modeler.AddEntitiesAsync(s, [Rect(0, 0, 10, 10), Circle(2, 2, 1), bad]));
Assert.Contains("[2]", ex.Message, StringComparison.Ordinal);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Face_center_point_lands_on_the_face()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddEntitiesAsync(s, [Rect(0, 0, 40, 20)], autoClose: true);
await _modeler.ExtrudeAsync(s, depth: 10);
await _modeler.RebuildAsync();
// Мост «индекс → точка»: точка обязана лежать на грани, иначе выбор по ней провалится.
var faces = await _query.ListFacesAsync();
Assert.NotEmpty(faces);
foreach (var f in faces)
{
var (x, y, z) = await _inspection.FaceCenterPointAsync(f.Index);
Assert.True(double.IsFinite(x) && double.IsFinite(y) && double.IsFinite(z),
$"грань [{f.Index}]: точка не вычислена");
// Точка внутри габарита детали (с допуском на округление).
Assert.InRange(x, -0.01, 40.01);
Assert.InRange(y, -0.01, 20.01);
Assert.InRange(z, -0.01, 10.01);
}
// И по такой точке эскиз на грани действительно создаётся.
var top = faces.First(f => f.Type == "plane");
var (px, py, pz) = await _inspection.FaceCenterPointAsync(top.Index);
var onFace = await _modeler.OpenSketchOnFaceAsync(px, py, pz);
await _modeler.CloseSketchAsync(onFace);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Fillet_and_chamfer_take_several_edges_at_once()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddEntitiesAsync(s, [Rect(0, 0, 40, 20)], autoClose: true);
await _modeler.ExtrudeAsync(s, depth: 10);
await _modeler.RebuildAsync();
var before = (await _query.GetPartInfoAsync()).Volume;
var vertical = (await _query.ListEdgesAsync()).Where(e => e.Type == "line").Take(4).Select(e => e.Index).ToList();
Assert.Equal(4, vertical.Count);
// Одна операция на все рёбра: материал убывает, дерево не растёт на четыре узла.
var featuresBefore = (await _inspection.ListFeaturesAsync()).Count;
await _modeler.FilletEdgesAsync(vertical, radius: 2);
await _modeler.RebuildAsync();
var after = (await _query.GetPartInfoAsync()).Volume;
var featuresAfter = (await _inspection.ListFeaturesAsync()).Count;
Assert.True(after < before, $"объём не убыл: {before} → {after}");
Assert.Equal(featuresBefore + 1, featuresAfter);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Set_variable_creates_then_updates()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddEntitiesAsync(s, [Rect(0, 0, 40, 20)], autoClose: true);
await _modeler.ExtrudeAsync(s, depth: 10);
var (created, wasCreated) = await _variables.SetOrCreateVariableAsync("width", "40", "ширина плиты");
Assert.True(wasCreated);
Assert.Equal(40, created, 3);
// Повторный вызов — уже изменение, и формула со ссылкой считается.
var (updated, wasCreatedAgain) = await _variables.SetOrCreateVariableAsync("width", "50");
Assert.False(wasCreatedAgain);
Assert.Equal(50, updated, 3);
var (derived, _) = await _variables.SetOrCreateVariableAsync("height", "width/2");
Assert.Equal(25, derived, 3);
var vars = await _inspection.ListVariablesAsync();
Assert.Contains(vars, v => v.Name == "width" && Math.Abs(v.Value - 50) < 1e-6);
Assert.Contains(vars, v => v.Name == "height");
}
finally { await _docs.CloseAsync(save: false); }
}
}