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 даже когда вычитание прошло мимо тела, поэтому сервис сверяет объём до и после и откатывает операцию, если ничего не изменилось.
117 lines
5.1 KiB
C#
117 lines
5.1 KiB
C#
using Kompas.Mcp.Core.Documents;
|
||
using Kompas.Mcp.Core.Modeling;
|
||
using Kompas.Mcp.Core.Query;
|
||
|
||
namespace Kompas.Mcp.Tests.Integration;
|
||
|
||
/// <summary>
|
||
/// Интеграция: элементарные тела (API7 <c>IElementaryBodies</c>) — второй путь формообразования
|
||
/// рядом с «эскиз → выдавливание», в том числе вычитание примитива вместо выреза по эскизу.
|
||
/// </summary>
|
||
[Trait("Category", "Integration")]
|
||
[Collection(KompasCollection.Name)]
|
||
public sealed class PrimitiveTests : IntegrationTestBase
|
||
{
|
||
private readonly DocumentService _docs;
|
||
private readonly PrimitiveService _primitives;
|
||
private readonly PartModeler _modeler;
|
||
private readonly QueryService _query;
|
||
|
||
public PrimitiveTests(KompasFixture fx) : base(fx)
|
||
{
|
||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||
_primitives = new PrimitiveService(fx.Session, fx.Dispatcher);
|
||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||
_query = new QueryService(fx.Session, fx.Dispatcher);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Block_is_built_by_sizes_from_the_anchor_corner()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
await _primitives.PrimitiveAsync(PrimitiveKind.Block, x: 4, y: 0, z: 0,
|
||
length: 82, width: 10.5, height: 3);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.MinX, 3.99, 4.01); // точка привязки — угол, а не центр
|
||
Assert.InRange(box.SizeX, 81.9, 82.1);
|
||
Assert.InRange(box.SizeY, 10.4, 10.6);
|
||
Assert.InRange(box.SizeZ, 2.99, 3.01);
|
||
|
||
var v = (await _query.GetPartInfoAsync()).Volume; // 82·10.5·3 = 2583
|
||
Assert.InRange(v, 2583 * 0.99, 2583 * 1.01);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Subtracted_block_hollows_a_pocket_without_a_sketch()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
await _primitives.PrimitiveAsync(PrimitiveKind.Block, 0, 0, 0, length: 40, width: 20, height: 10);
|
||
// Карман 30×10 глубиной 4 — вычитаем блок, приподняв его до дна кармана.
|
||
await _primitives.PrimitiveAsync(PrimitiveKind.Block, 5, 5, 6, length: 30, width: 10, height: 10,
|
||
result: PrimitiveResult.Subtract);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeZ, 9.9, 10.1); // габарит не изменился — вырезано внутри
|
||
|
||
var v = (await _query.GetPartInfoAsync()).Volume; // 40·20·10 − 30·10·4 = 6800
|
||
Assert.InRange(v, 6800 * 0.99, 6800 * 1.01);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Cylinder_can_be_added_to_an_existing_body()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
await _primitives.PrimitiveAsync(PrimitiveKind.Block, 0, 0, 0, length: 20, width: 20, height: 5);
|
||
await _primitives.PrimitiveAsync(PrimitiveKind.Cylinder, 10, 10, 5, diameter: 8, height: 10,
|
||
result: PrimitiveResult.Union);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var bodies = await _query.ListFacesAsync();
|
||
Assert.NotEmpty(bodies);
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeZ, 14.9, 15.1); // 5 плиты + 10 цилиндра
|
||
|
||
var v = (await _query.GetPartInfoAsync()).Volume; // 20·20·5 + π·4²·10 ≈ 2502.7
|
||
var expected = 20 * 20 * 5 + Math.PI * 16 * 10;
|
||
Assert.InRange(v, expected * 0.98, expected * 1.02);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Subtracting_where_nothing_intersects_fails_with_a_hint()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
await _primitives.PrimitiveAsync(PrimitiveKind.Block, 0, 0, 0, length: 10, width: 10, height: 10);
|
||
|
||
// Update() при вычитании мимо тела возвращает TRUE — ловим по неизменившемуся объёму.
|
||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||
_primitives.PrimitiveAsync(PrimitiveKind.Sphere, 100, 100, 100, diameter: 5,
|
||
result: PrimitiveResult.Subtract));
|
||
|
||
Assert.Contains("не удалило материал", ex.Message, StringComparison.Ordinal);
|
||
|
||
// Неудачная операция не должна оставаться в дереве: объём прежний, тело одно.
|
||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.InRange(v, 1000 * 0.99, 1000 * 1.01);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
}
|