bc0adaf830
- CoordinateAxis enum + CoordinateAxes.ToObj3dType/Parse (X/Y/Z → o3d_axisO*) - SketchGeometry.RequireMin; PartModeler.RequireFeature (источник по id из _features) - LinearPatternAsync: SetAxis1 + SetCopyParamAlongAxis(true,0,count,step,false), count2=1 - инструмент linear_pattern; unit-тесты (RequireMin, Parse), интеграционный тест (3 экз. ±5%) - эмпирически: count включает исходный, step мм между соседними, count2=1 выключает 2D
198 lines
9.6 KiB
C#
198 lines
9.6 KiB
C#
using Kompas.Mcp.Core.Documents;
|
||
using Kompas.Mcp.Core.Modeling;
|
||
using Kompas.Mcp.Core.Query;
|
||
|
||
namespace Kompas.Mcp.Tests.Integration;
|
||
|
||
/// <summary>Интеграция: формообразующие операции пакета B.</summary>
|
||
[Trait("Category", "Integration")]
|
||
[Collection(KompasCollection.Name)]
|
||
public sealed class FeatureOpsTests
|
||
{
|
||
private readonly DocumentService _docs;
|
||
private readonly PartModeler _modeler;
|
||
private readonly QueryService _query;
|
||
|
||
public FeatureOpsTests(KompasFixture fx)
|
||
{
|
||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||
_query = new QueryService(fx.Session, fx.Dispatcher);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Shell_box_hollows_with_open_cap()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Коробка 40×30×20.
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
await _modeler.AddRectangleAsync(s, 0, 0, 40, 30);
|
||
await _modeler.CloseSketchAsync(s);
|
||
await _modeler.ExtrudeAsync(s, depth: 20);
|
||
var before = (await _query.GetPartInfoAsync()).Volume; // 24000
|
||
|
||
// Удаляем один из двух плоских торцов (площадь 40×30=1200) → открытая оболочка.
|
||
// Какой именно торец (верх/низ) — для объёма не важно: результат симметричен.
|
||
var faces = await _query.ListFacesAsync();
|
||
var cap = faces.First(f => f.Type == "plane" && Math.Abs(f.Area - 1200) < 1);
|
||
var id = await _modeler.ShellAsync(new[] { cap.Index }, thickness: 2, outward: false);
|
||
Assert.True(id > 0);
|
||
await _modeler.RebuildAsync();
|
||
|
||
// Оболочка 2 мм с открытым торцом: 24000 − 36×26×18 = 7152 мм³.
|
||
var after = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.True(after < before, "После оболочки объём должен уменьшиться.");
|
||
Assert.InRange(after, 7152 * 0.95, 7152 * 1.05);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Rib_adds_material_in_corner_of_L_bracket()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Г-образный уголок: L-профиль на XOZ (замкнутая ломаная), выдавить по Y на 30.
|
||
// Площадь L = 40×5 + 5×35 = 375 → объём 375·30 = 11250.
|
||
var sBody = await _modeler.OpenSketchAsync(BasePlane.XOZ);
|
||
var lprofile = new (double x, double y)[] { (0, 0), (40, 0), (40, 5), (5, 5), (5, 40), (0, 40) };
|
||
await _modeler.AddPolylineAsync(sBody, lprofile, closed: true);
|
||
await _modeler.CloseSketchAsync(sBody);
|
||
await _modeler.ExtrudeAsync(sBody, depth: 30);
|
||
var before = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.InRange(before, 11250 * 0.95, 11250 * 1.05); // L-уголок 375·30
|
||
|
||
// Ребро-косынка: разомкнутый отрезок во внутреннем углу, на XOZ (Y=0).
|
||
// Контур висит в зазоре (не касается полок); side=left, несимметрично → толщина 5 мм.
|
||
var sRib = await _modeler.OpenSketchAsync(BasePlane.XOZ);
|
||
await _modeler.AddLineAsync(sRib, 25, 10, 10, 25); // висит в пустом углу, не касаясь полок
|
||
await _modeler.CloseSketchAsync(sRib);
|
||
var id = await _modeler.RibAsync(sRib, thickness: 5, side: "left", symmetric: false);
|
||
Assert.True(id > 0);
|
||
await _modeler.RebuildAsync();
|
||
|
||
// Косынка добавляет материал: объём растёт, но в разумных пределах.
|
||
var after = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.True(after > before, "После ребра жёсткости объём должен увеличиться.");
|
||
Assert.InRange(after, before, before * 1.3);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Sweep_circle_along_line_makes_cylinder()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Профиль: окружность R5 на XOY (центр 0,0).
|
||
var profile = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
await _modeler.AddCircleAsync(profile, 0, 0, 5);
|
||
await _modeler.CloseSketchAsync(profile);
|
||
// Траектория: вертикальный отрезок (0,0)→(0,50) на XOZ (по +Z из центра профиля).
|
||
var path = await _modeler.OpenSketchAsync(BasePlane.XOZ);
|
||
await _modeler.AddLineAsync(path, 0, 0, 0, 50);
|
||
await _modeler.CloseSketchAsync(path);
|
||
|
||
var id = await _modeler.SweepAsync(profile, path);
|
||
Assert.True(id > 0);
|
||
await _modeler.RebuildAsync();
|
||
|
||
// Окружность R5 по прямой длиной 50 → цилиндр: π·25·50 ≈ 3927 мм³.
|
||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||
var expected = Math.PI * 25 * 50;
|
||
Assert.InRange(v, expected * 0.95, expected * 1.05);
|
||
|
||
// Габарит подтверждает ось: цилиндр R5 вдоль Z → ≈10×10×50.
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeX, 10 * 0.95, 10 * 1.05);
|
||
Assert.InRange(box.SizeY, 10 * 0.95, 10 * 1.05);
|
||
Assert.InRange(box.SizeZ, 50 * 0.95, 50 * 1.05);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task OffsetPlane_sketch_extrudes_at_height()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Эскиз на XOY, смещённой на +20 по Z → цилиндр R5×H10 с основанием на Z≈20.
|
||
var s = await _modeler.OpenSketchOnOffsetPlaneAsync(BasePlane.XOY, offset: 20);
|
||
await _modeler.AddCircleAsync(s, 0, 0, 5);
|
||
await _modeler.CloseSketchAsync(s);
|
||
await _modeler.ExtrudeAsync(s, depth: 10);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.MinZ, 19.5, 20.5); // основание поднято на 20 мм
|
||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.InRange(v, Math.PI * 25 * 10 * 0.95, Math.PI * 25 * 10 * 1.05);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task LinearPattern_replicates_cylinder_along_X()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Цилиндр R5×H10 в центре (объём π·25·10 ≈ 785.4 мм³).
|
||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
await _modeler.AddCircleAsync(s, 0, 0, 5);
|
||
await _modeler.CloseSketchAsync(s);
|
||
var cyl = await _modeler.ExtrudeAsync(s, depth: 10);
|
||
var one = (await _query.GetPartInfoAsync()).Volume;
|
||
|
||
// Линейный массив: 3 экземпляра вдоль X с шагом 30 (R5 → не перекрываются).
|
||
var id = await _modeler.LinearPatternAsync(new[] { cyl }, CoordinateAxis.X, count: 3, step: 30);
|
||
Assert.True(id > 0);
|
||
await _modeler.RebuildAsync();
|
||
|
||
// count=3 включает исходный → суммарный объём ≈ 3 цилиндра.
|
||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||
Assert.InRange(v, one * 3 * 0.95, one * 3 * 1.05);
|
||
|
||
// Габарит вдоль X: копии в 0/30/60, R5 → от −5 до 65 → размер ≈70 (знак шага не важен).
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeX, 70 * 0.95, 70 * 1.05);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Loft_two_squares_makes_solid()
|
||
{
|
||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||
try
|
||
{
|
||
// Два квадратных сечения 20×20 на XOY (Z=0) и на XOY+40 → призма 20×20×40.
|
||
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||
await _modeler.AddRectangleAsync(s1, -10, -10, 10, 10);
|
||
await _modeler.CloseSketchAsync(s1);
|
||
var s2 = await _modeler.OpenSketchOnOffsetPlaneAsync(BasePlane.XOY, offset: 40);
|
||
await _modeler.AddRectangleAsync(s2, -10, -10, 10, 10);
|
||
await _modeler.CloseSketchAsync(s2);
|
||
|
||
var id = await _modeler.LoftAsync(new[] { s1, s2 });
|
||
Assert.True(id > 0);
|
||
await _modeler.RebuildAsync();
|
||
|
||
var v = (await _query.GetPartInfoAsync()).Volume; // 20×20×40 = 16000
|
||
Assert.InRange(v, 16000 * 0.9, 16000 * 1.1);
|
||
|
||
// Габарит подтверждает соединение сечений по высоте: ≈20×20×40.
|
||
var box = await _query.GetBoundingBoxAsync();
|
||
Assert.InRange(box.SizeX, 20 * 0.95, 20 * 1.05);
|
||
Assert.InRange(box.SizeY, 20 * 0.95, 20 * 1.05);
|
||
Assert.InRange(box.SizeZ, 40 * 0.95, 40 * 1.05);
|
||
}
|
||
finally { await _docs.CloseAsync(save: false); }
|
||
}
|
||
}
|