Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/InspectionTextTests.cs
T
mikhail 28d2ae3868 feat(inspection): структурное «зрение» модели — осмотр без снимка
Новый слой «модель как данные» (приоритетный над 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>
2026-05-26 18:44:38 +03:00

179 lines
8.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}