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>
179 lines
8.6 KiB
C#
179 lines
8.6 KiB
C#
using Kompas.Mcp.Core.Query;
|
||
|
||
namespace Kompas.Mcp.Tests;
|
||
|
||
/// <summary>Юнит-тесты чистого рендера/группировки структурного «паспорта». COM не требуется.</summary>
|
||
[Trait("Category", "Unit")]
|
||
public sealed class InspectionTextTests
|
||
{
|
||
// ── Детект пустой модели по габариту ──────────────────────────────────
|
||
[Fact]
|
||
public void IsEmptyGabarit_true_for_sentinel()
|
||
=> Assert.True(InspectionText.IsEmptyGabarit(double.MaxValue, double.MaxValue, double.MaxValue));
|
||
|
||
[Fact]
|
||
public void IsEmptyGabarit_true_for_all_zero()
|
||
=> Assert.True(InspectionText.IsEmptyGabarit(0, 0, 0));
|
||
|
||
[Fact]
|
||
public void IsEmptyGabarit_false_for_real_part()
|
||
=> Assert.False(InspectionText.IsEmptyGabarit(40, 20, 15));
|
||
|
||
// ── Группировка типов ────────────────────────────────────────────────
|
||
[Fact]
|
||
public void GroupTypes_orders_by_count_desc()
|
||
{
|
||
var s = InspectionText.GroupTypes(new[] { "plane", "cylinder", "plane", "plane", "cylinder" });
|
||
Assert.Equal("3 plane, 2 cylinder", s);
|
||
}
|
||
|
||
[Fact]
|
||
public void GroupTypes_empty_returns_dash()
|
||
=> Assert.Equal("—", InspectionText.GroupTypes(Array.Empty<string>()));
|
||
|
||
// ── Детект импорта без истории ───────────────────────────────────────
|
||
private static FeatureInfo Feat(int i, string name, string kind) => new() { Index = i, Name = name, Kind = kind };
|
||
|
||
[Fact]
|
||
public void IsImported_true_for_bare_origin_and_body()
|
||
{
|
||
var fs = new[] { Feat(0, "Начало координат", "origin"), Feat(1, "Body1", "other") };
|
||
Assert.True(InspectionText.IsImported(fs, bodyCount: 1));
|
||
}
|
||
|
||
[Fact]
|
||
public void IsImported_false_when_tree_has_edit_history()
|
||
{
|
||
// Импорт, отредактированный move_face: origin + тело + 5 узлов-правок (>2) — это уже история.
|
||
var fs = new[]
|
||
{
|
||
Feat(0, "Начало координат", "origin"), Feat(1, "Body1", "other"),
|
||
Feat(2, "Смещенная плоскость:1", "other"), Feat(3, "Разрезать:1", "other"),
|
||
Feat(4, "Переместить грани:1", "other"), Feat(5, "Булева операция:1", "other"),
|
||
};
|
||
Assert.False(InspectionText.IsImported(fs, bodyCount: 1));
|
||
}
|
||
|
||
[Fact]
|
||
public void IsImported_false_for_native_part_with_operations()
|
||
{
|
||
var fs = new[]
|
||
{
|
||
Feat(0, "Начало координат", "origin"), Feat(1, "Эскиз:1", "sketch"),
|
||
Feat(2, "Элемент выдавливания:1", "extrusion"), Feat(3, "Скругление:1", "fillet"),
|
||
};
|
||
Assert.False(InspectionText.IsImported(fs, bodyCount: 1));
|
||
}
|
||
|
||
[Fact]
|
||
public void IsImported_false_without_body()
|
||
=> Assert.False(InspectionText.IsImported(new[] { Feat(0, "Начало координат", "origin") }, bodyCount: 0));
|
||
|
||
// ── Рендер: родная параметрическая деталь ────────────────────────────
|
||
[Fact]
|
||
public void Render_native_part_shows_tree_with_params()
|
||
{
|
||
var d = new ModelDescription
|
||
{
|
||
Name = "demo",
|
||
IsEmpty = false,
|
||
Box = new BoundingBox { MinX = -20, MinY = -10, MinZ = 0, MaxX = 20, MaxY = 10, MaxZ = 15 },
|
||
Mass = new PartInfo { Volume = 11961, Mass = 0.094, Area = 3370, CenterX = 0, CenterY = 0, CenterZ = 7.5 },
|
||
Bodies = new[] { new BodyInfo { Index = 0, IsSolid = true, FaceCount = 7 } },
|
||
FaceTypes = new[] { "plane", "plane", "cylinder" },
|
||
EdgeTypes = new[] { "line", "line" },
|
||
Imported = false,
|
||
Features = new[]
|
||
{
|
||
new FeatureInfo { Index = 0, Name = "Начало координат", Kind = "origin" },
|
||
new FeatureInfo { Index = 1, Name = "Эскиз:1", Kind = "sketch" },
|
||
new FeatureInfo { Index = 2, Name = "Элемент выдавливания:1", Kind = "extrusion", Detail = "глубина 15.00 мм" },
|
||
new FeatureInfo { Index = 3, Name = "Скругление:1", Kind = "fillet", Detail = "R3.00 мм" },
|
||
},
|
||
Variables = Array.Empty<VariableInfo>(),
|
||
};
|
||
|
||
var text = InspectionText.Render(d);
|
||
|
||
Assert.Contains("Документ: «demo»", text);
|
||
Assert.Contains("Габарит (Д×Ш×В): 40.00 × 20.00 × 15.00 мм", text);
|
||
Assert.Contains("Тела: 1", text);
|
||
Assert.Contains("граней 3 (2 plane, 1 cylinder)", text);
|
||
Assert.Contains("Дерево построения (4", text);
|
||
Assert.Contains("«Элемент выдавливания:1» — глубина 15.00 мм", text);
|
||
Assert.Contains("«Скругление:1» — R3.00 мм", text);
|
||
Assert.Contains("Переменные: нет", text);
|
||
}
|
||
|
||
// ── Рендер: импортированная модель (флаг Imported) ───────────────────
|
||
[Fact]
|
||
public void Render_imported_model_notes_no_history()
|
||
{
|
||
var d = new ModelDescription
|
||
{
|
||
Name = "top_spacer",
|
||
IsEmpty = false,
|
||
Box = new BoundingBox { MinX = 0, MinY = 0, MinZ = 0, MaxX = 18, MaxY = 8, MaxZ = 39 },
|
||
Mass = new PartInfo { Volume = 2463, Mass = 0.019, Area = 2053, CenterX = 0, CenterY = 0, CenterZ = 21 },
|
||
Bodies = new[] { new BodyInfo { Index = 0, IsSolid = true, FaceCount = 61 } },
|
||
FaceTypes = new[] { "plane", "cylinder" },
|
||
EdgeTypes = new[] { "line" },
|
||
Imported = true,
|
||
Features = new[]
|
||
{
|
||
new FeatureInfo { Index = 0, Name = "Начало координат", Kind = "origin" },
|
||
new FeatureInfo { Index = 1, Name = "Body1", Kind = "body" },
|
||
},
|
||
Variables = Array.Empty<VariableInfo>(),
|
||
};
|
||
|
||
var text = InspectionText.Render(d);
|
||
|
||
Assert.Contains("импортирован", text.ToLowerInvariant());
|
||
Assert.DoesNotContain("Дерево построения (", text); // не показываем как параметрическое дерево
|
||
}
|
||
|
||
// ── Рендер: пустая модель ────────────────────────────────────────────
|
||
[Fact]
|
||
public void Render_empty_model()
|
||
{
|
||
var d = new ModelDescription
|
||
{
|
||
Name = "empty", IsEmpty = true,
|
||
Bodies = Array.Empty<BodyInfo>(), FaceTypes = Array.Empty<string>(), EdgeTypes = Array.Empty<string>(),
|
||
Imported = false, Features = Array.Empty<FeatureInfo>(), Variables = Array.Empty<VariableInfo>(),
|
||
};
|
||
|
||
var text = InspectionText.Render(d);
|
||
|
||
Assert.Contains("Модель пуста", text);
|
||
}
|
||
|
||
// ── Рендер переменных ────────────────────────────────────────────────
|
||
[Fact]
|
||
public void Render_variables_listed_with_flags()
|
||
{
|
||
var d = new ModelDescription
|
||
{
|
||
Name = "p", IsEmpty = false,
|
||
Box = new BoundingBox { MinX = 0, MinY = 0, MinZ = 0, MaxX = 1, MaxY = 1, MaxZ = 1 },
|
||
Mass = new PartInfo { Volume = 1, Mass = 1, Area = 6, CenterX = 0, CenterY = 0, CenterZ = 0 },
|
||
Bodies = new[] { new BodyInfo { Index = 0, IsSolid = true, FaceCount = 6 } },
|
||
FaceTypes = new[] { "plane" }, EdgeTypes = new[] { "line" },
|
||
Imported = false, Features = Array.Empty<FeatureInfo>(),
|
||
Variables = new[]
|
||
{
|
||
new VariableInfo { Name = "L", Expression = "80", Value = 80, External = true, Information = false },
|
||
new VariableInfo { Name = "v", Expression = "L/2", Value = 40, External = false, Information = true },
|
||
},
|
||
};
|
||
|
||
var text = InspectionText.Render(d);
|
||
|
||
Assert.Contains("Переменные (2):", text);
|
||
Assert.Contains("L = 80", text);
|
||
Assert.Contains("[внешняя]", text);
|
||
Assert.Contains("[инфо]", text);
|
||
}
|
||
}
|