feat(sketch): 7 примитивов эскиза — дуга x2, эллипс, ломаная, многоугольник, сплайн, точка
Методы PartModeler + MCP-инструменты sketch_add_arc_3points/arc/ellipse/ polyline/polygon/spline/point. NURBS порядок 4, ksEndObj через try/finally, param-структуры через NewParam с освобождением RCW. 7 интеграционных тестов (профиль → выдавливание → объём) зелёные. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
using Kompas.Mcp.Core.Query;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: новые примитивы эскиза (профиль → выдавливание → объём).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class SketchPrimitivesTests
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly QueryService _query;
|
||||
|
||||
public SketchPrimitivesTests(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 Arc3Points_semicircle_extrudes()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Верхний полукруг R10: дуга (10,0)→(0,10)→(-10,0) + хорда обратно.
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddArc3PointsAsync(s, 10, 0, 0, 10, -10, 0);
|
||||
await _modeler.AddLineAsync(s, -10, 0, 10, 0);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 5);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // π·10²/2·5 ≈ 785.4
|
||||
var expected = Math.PI * 100 / 2 * 5;
|
||||
Assert.InRange(v, expected * 0.95, expected * 1.05);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArcByAngle_quarter_sector_extrudes()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Сектор 90° R10 в первом квадранте: дуга (10,0)→(0,10) + два радиуса к центру.
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddArcByAngleAsync(s, 0, 0, 10, 0, 90, counterClockwise: true);
|
||||
await _modeler.AddLineAsync(s, 0, 10, 0, 0);
|
||||
await _modeler.AddLineAsync(s, 0, 0, 10, 0);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 5);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // π·10²/4·5 ≈ 392.7
|
||||
var expected = Math.PI * 100 / 4 * 5;
|
||||
Assert.InRange(v, expected * 0.95, expected * 1.05);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ellipse_extrudes_to_expected_volume()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddEllipseAsync(s, 0, 0, semiMajor: 10, semiMinor: 5);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 4);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // π·10·5·4 ≈ 628.3
|
||||
var expected = Math.PI * 10 * 5 * 4;
|
||||
Assert.InRange(v, expected * 0.97, expected * 1.03);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Polyline_closed_triangle_extrudes()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Прямоугольный треугольник (0,0)-(20,0)-(0,15), площадь 150.
|
||||
var pts = new (double x, double y)[] { (0, 0), (20, 0), (0, 15) };
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddPolylineAsync(s, pts, closed: true);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 5);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // 150·5 = 750
|
||||
Assert.InRange(v, 750 * 0.97, 750 * 1.03);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Polygon_hexagon_extrudes_to_expected_volume()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Правильный 6-угольник, вписанный в окружность R10: площадь = 0.5·6·R²·sin(60°) ≈ 259.8.
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddPolygonAsync(s, 0, 0, vertexCount: 6, radius: 10, inscribed: true);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 5);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||||
var area = 0.5 * 6 * 100 * Math.Sin(2 * Math.PI / 6);
|
||||
Assert.InRange(v, area * 5 * 0.95, area * 5 * 1.05);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Spline_closed_loop_extrudes()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Замкнутый сплайн по 4 точкам вокруг начала координат → выпуклая «капля».
|
||||
var pts = new (double x, double y)[] { (10, 0), (0, 10), (-10, 0), (0, -10) };
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddSplineAsync(s, pts, closed: true);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 5);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
// Замкнутый кубический сплайн через 4 точки даёт гладкий вогнутый контур, вписанный
|
||||
// в габаритный квадрат 20×20 (площадь профиля ≈135 мм²). Проверяем, что тело построено
|
||||
// и объём положителен и не превышает габарит профиля (площадь ≤ 400 → V ≤ 2000).
|
||||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||||
Assert.InRange(v, 50 * 5, 400 * 5);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Point_does_not_break_sketch()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Окружность R10 + точка в центре; выдавливание должно дать цилиндр без искажений.
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddCircleAsync(s, 0, 0, 10);
|
||||
await _modeler.AddPointAsync(s, 0, 0);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 5);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // π·100·5 ≈ 1570.8
|
||||
var expected = Math.PI * 100 * 5;
|
||||
Assert.InRange(v, expected * 0.97, expected * 1.03);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user