31b933e1cc
ksConvertTextToCurve ВОЗВРАЩАЕТ ссылку на кривые, и только по ней ksGetObjGabaritRect даёт габарит букв: по текстовой ссылке это ячейка строки (Arial h=10 «HH» — 23.75 против 19.97 у глифов, второе совпадает с выдавленным телом). Агент вписывает надпись в поле по габариту, поэтому в ответе теперь оба числа. OutputPath.Prepare создаёт каталог назначения и делает путь абсолютным (document_save, export_step, снимок): КОМПАС в несуществующую папку молча не пишет, а диагностика при этом винила занятое имя файла — чинили не ту причину. Причины разведены.
157 lines
7.2 KiB
C#
157 lines
7.2 KiB
C#
using Kompas.Mcp.Core.Documents;
|
||
using Kompas.Mcp.Core.Modeling;
|
||
using Kompas.Mcp.Core.Query;
|
||
|
||
namespace Kompas.Mcp.Tests.Integration;
|
||
|
||
/// <summary>
|
||
/// Интеграция: надпись в эскизе. Текст сам по себе — оформление, выдавить его нельзя;
|
||
/// примитив type=text переводит его в кривые (ksConvertTextToCurve), и контур глифов
|
||
/// становится обычным сечением операции.
|
||
/// </summary>
|
||
[Trait("Category", "Integration")]
|
||
[Collection(KompasCollection.Name)]
|
||
public sealed class SketchTextTests : IntegrationTestBase
|
||
{
|
||
private readonly DocumentService _docs;
|
||
private readonly PartModeler _modeler;
|
||
private readonly QueryService _query;
|
||
|
||
public SketchTextTests(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);
|
||
}
|
||
|
||
private static SketchEntity Text(string text, double x, double y, double height, string? font = null) => new()
|
||
{
|
||
Kind = SketchEntityKind.Text,
|
||
Points = [(x, y)],
|
||
Text = text,
|
||
Height = height,
|
||
FontName = font,
|
||
};
|
||
|
||
[Fact]
|
||
public async Task Text_becomes_curves_and_extrudes()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
var notes = await _modeler.AddEntitiesAsync(s, [Text("HI", 0, 0, 10, "Arial")], autoClose: true);
|
||
await _modeler.ExtrudeAsync(s, depth: 2);
|
||
await _modeler.RebuildAsync();
|
||
|
||
// Метрики нужны агенту для выравнивания: до выдавливания их узнать неоткуда.
|
||
Assert.Contains("глифы", notes[0], StringComparison.Ordinal);
|
||
Assert.Contains("ширина ячейки", notes[0], StringComparison.Ordinal);
|
||
|
||
var info = await _query.GetPartInfoAsync();
|
||
Assert.True(info.Volume > 0, "надпись не дала объёма — текст не переведён в кривые");
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeZ, 1.99, 2.01);
|
||
Assert.InRange(box.SizeY, 6, 11); // высота прописных ≈ height
|
||
Assert.True(box.SizeX > box.SizeY); // «HI» шире, чем выше
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Reported_glyph_box_matches_the_extruded_geometry()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Ширина ячейки (её и возвращали раньше) больше габарита глифов, поэтому агент,
|
||
// вписывающий надпись в поле, промахивался. Сверяем заявленный габарит с фактическим.
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
var notes = await _modeler.AddEntitiesAsync(s, [Text("HH", 0, 0, 10, "Arial")], autoClose: true);
|
||
await _modeler.ExtrudeAsync(s, depth: 2);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var numbers = System.Text.RegularExpressions.Regex
|
||
.Matches(notes[0], @"-?\d+\.\d{2}")
|
||
.Select(m => double.Parse(m.Value, System.Globalization.CultureInfo.InvariantCulture))
|
||
.ToList();
|
||
Assert.True(numbers.Count >= 7, $"в заметке нет чисел габарита: {notes[0]}");
|
||
var (minX, maxX, minY, maxY) = (numbers[0], numbers[1], numbers[2], numbers[3]);
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(maxX - minX, box.SizeX - 0.05, box.SizeX + 0.05);
|
||
Assert.InRange(maxY - minY, box.SizeY - 0.05, box.SizeY + 0.05);
|
||
|
||
// Единицы — миллиметры эскиза, а не сантиметры листа: высота прописной ≈ height.
|
||
Assert.InRange(maxY - minY, 9.5, 10.5);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Unknown_font_is_reported_by_name()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
var bad = Text("HI", 0, 0, 10, "No Such Font 12345");
|
||
|
||
// КОМПАС подставляет замену вместо отсутствующего шрифта, поэтому надпись
|
||
// всё равно строится — важно, что она не рушит пакет и даёт геометрию.
|
||
var notes = await _modeler.AddEntitiesAsync(s, [bad], autoClose: true);
|
||
Assert.False(string.IsNullOrEmpty(notes[0]));
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Thin_wall_outward_grows_the_contour()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Квадрат 20×20 стенкой 2 мм наружу: габарит вырастает на 2·2 мм по каждой оси,
|
||
// а объём равен объёму рамки, а не сплошной плиты.
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
await _modeler.AddRectangleAsync(s, 0, 0, 20, 20);
|
||
await _modeler.CloseSketchAsync(s);
|
||
await _modeler.ExtrudeAsync(s, depth: 5, thinThickness: 2, thinSide: ThinSide.Outward);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeX, 23.9, 24.1);
|
||
Assert.InRange(box.SizeY, 23.9, 24.1);
|
||
|
||
var v = (await _query.GetPartInfoAsync()).Volume; // (24² − 20²)·5 = 880
|
||
Assert.InRange(v, 880 * 0.98, 880 * 1.02);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Thin_wall_inward_keeps_the_contour_and_hollows_it()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Стенка внутрь контура: габарит остаётся 20×20, материал — только рамка по периметру.
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
await _modeler.AddRectangleAsync(s, 0, 0, 20, 20);
|
||
await _modeler.CloseSketchAsync(s);
|
||
await _modeler.ExtrudeAsync(s, depth: 5, thinThickness: 2, thinSide: ThinSide.Inward);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeX, 19.9, 20.1);
|
||
Assert.InRange(box.SizeY, 19.9, 20.1);
|
||
|
||
// (20² − 16²)·5 = 720; сплошная плита дала бы 2000 — так ловится «стенка не применилась».
|
||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.InRange(v, 720 * 0.98, 720 * 1.02);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
}
|