28d2ae3868
Новый слой «модель как данные» (приоритетный над model_snapshot): - describe_model — структурный «паспорт» детали одним вызовом (габарит, МЦХ, тела, сводка топологии, дерево построения с параметрами, переменные) - list_features / list_bodies / list_variables - describe_face / describe_edge (drill-down по индексу) - measure (расстояние/угол между гранями/рёбрами/вершинами) - model_snapshot понижен до fallback для визуально-пространственных вопросов Реализация: ModelInspectionService (COM) + чистый InspectionText (рендер, группировка, детект «импорт без истории») + record-модели; InspectionTools. Дерево читается через ksPart.GetFeature()->SubFeatureCollection; точный тип и параметры операций берутся из GetObject()->GetDefinition() (ksFeature.type отдаёт лишь coarse o3d_entity). Снимок дерева также включает слои conversion (STEP), editing (move_face/solid) и validation, развивавшиеся параллельно. Полный прогон: 61 тест зелёный, 43 MCP-инструмента. Документация (README, ARCHITECTURE, OPEN_QUESTIONS, CLAUDE.md, presentation.html) синхронизирована. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
4.1 KiB
C#
115 lines
4.1 KiB
C#
using Kompas.Mcp.Core.Documents;
|
|
using Kompas.Mcp.Core.Modeling;
|
|
using Kompas.Mcp.Core.Query;
|
|
|
|
namespace Kompas.Mcp.Tests.Integration;
|
|
|
|
/// <summary>Интеграция: структурное «зрение» — дерево, топология, drill-down, измерения.</summary>
|
|
[Trait("Category", "Integration")]
|
|
[Collection(KompasCollection.Name)]
|
|
public sealed class InspectionTests
|
|
{
|
|
private readonly DocumentService _docs;
|
|
private readonly PartModeler _modeler;
|
|
private readonly ModelInspectionService _inspect;
|
|
|
|
public InspectionTests(KompasFixture fx)
|
|
{
|
|
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
|
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
|
_inspect = new ModelInspectionService(fx.Session, fx.Dispatcher);
|
|
}
|
|
|
|
/// <summary>Построить эталон: прямоугольник 40×20 → выдавить 15 мм → скругление R3 первого ребра.</summary>
|
|
private async Task BuildDemoPartAsync()
|
|
{
|
|
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
|
await _modeler.AddRectangleAsync(s, -20, -10, 20, 10);
|
|
await _modeler.CloseSketchAsync(s);
|
|
await _modeler.ExtrudeAsync(s, depth: 15);
|
|
await _modeler.FilletEdgeIndexAsync(0, 3);
|
|
await _modeler.RebuildAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Describe_model_shows_tree_topology_and_bodies()
|
|
{
|
|
await _docs.CreateAsync(KompasDocumentType.Part);
|
|
try
|
|
{
|
|
await BuildDemoPartAsync();
|
|
|
|
var d = await _inspect.DescribeModelAsync();
|
|
|
|
Assert.False(d.IsEmpty);
|
|
Assert.False(d.Imported); // родная параметрическая деталь
|
|
Assert.Single(d.Bodies); // одно тело
|
|
Assert.True(d.Bodies[0].IsSolid);
|
|
|
|
// Габарит исходного бруска 40×20×15 (скругление R3 не меняет общий габарит).
|
|
Assert.NotNull(d.Box);
|
|
Assert.InRange(d.Box!.SizeX, 39.9, 40.1);
|
|
Assert.InRange(d.Box!.SizeZ, 14.9, 15.1);
|
|
|
|
// Топология: есть плоскости и цилиндр от скругления.
|
|
Assert.Contains("plane", d.FaceTypes);
|
|
Assert.Contains("cylinder", d.FaceTypes);
|
|
|
|
// Дерево: выдавливание с глубиной и скругление.
|
|
Assert.Contains(d.Features, f => f.Kind == "extrusion" && (f.Detail ?? "").Contains("15"));
|
|
Assert.Contains(d.Features, f => f.Kind == "fillet");
|
|
}
|
|
finally { await _docs.CloseAsync(save: false); }
|
|
}
|
|
|
|
[Fact]
|
|
public async Task List_features_reports_extrusion_depth()
|
|
{
|
|
await _docs.CreateAsync(KompasDocumentType.Part);
|
|
try
|
|
{
|
|
await BuildDemoPartAsync();
|
|
|
|
var fs = await _inspect.ListFeaturesAsync();
|
|
|
|
var extrusion = Assert.Single(fs, f => f.Kind == "extrusion");
|
|
Assert.Contains("15", extrusion.Detail ?? "");
|
|
Assert.Contains(fs, f => f.Kind == "fillet");
|
|
}
|
|
finally { await _docs.CloseAsync(save: false); }
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Describe_face_returns_area_and_edges()
|
|
{
|
|
await _docs.CreateAsync(KompasDocumentType.Part);
|
|
try
|
|
{
|
|
await BuildDemoPartAsync();
|
|
|
|
var face = await _inspect.DescribeFaceAsync(0);
|
|
|
|
Assert.True(face.Area > 0);
|
|
Assert.True(face.EdgeCount > 0);
|
|
Assert.NotNull(face.Normal);
|
|
}
|
|
finally { await _docs.CloseAsync(save: false); }
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Measure_between_two_faces_succeeds()
|
|
{
|
|
await _docs.CreateAsync(KompasDocumentType.Part);
|
|
try
|
|
{
|
|
await BuildDemoPartAsync();
|
|
|
|
var m = await _inspect.MeasureAsync("face", 0, "face", 1);
|
|
|
|
Assert.True(m.MinDistance >= 0); // путь измерения работает
|
|
Assert.True(double.IsFinite(m.Distance));
|
|
}
|
|
finally { await _docs.CloseAsync(save: false); }
|
|
}
|
|
}
|