Инструменты v1 ГОТОВО
-
26 инструментов, отдаются по MCP-протоколу
+
28 инструментов, отдаются по MCP-протоколу
- ✓
System: connect · status · set_visible
- ✓
Documents: create · open · save · save_as · close · active
- - ✓
Sketch: create · create_on_face · add line/circle/rectangle/axis · close
+ - ✓
Sketch: create · on_face (точка/индекс) · add line/circle/rectangle/axis · close
- ✓
Features: extrude · revolve · fillet_edge/chamfer_edge 🆕 · rebuild
- ✓
Vision: model_snapshot 👁️
- - ✓
Query: get_part_info · get_bounding_box 🆕
- Query: list_bodies · list_faces (далее)
+ - ✓
Query: get_part_info · get_bounding_box · list_faces 🆕
+ Query: list_bodies · list_edges (далее)
v1 · готово ✓
Цикл моделирования
Документы, эскизы, выдавливание/вырез, снимок модели — работает
-
v2 · в работе
Операции и выборка
Эскиз на грани ✓ · вращение ✓ · скругление/фаска ✓ · далее: массивы, список тел
+
v2 · в работе
Операции и выборка
Эскиз на грани ✓ · вращение ✓ · скругление/фаска ✓ · список граней ✓ · далее: массивы
v3
Параметрика
Переменные IVariable7, свойства IPropertyMng, формулы
v4
2D-черчение
Виды, размеры, тексты, штриховки; спецификации
v5
Сборки и экспорт
Компоненты, сопряжения, конвертеры (STEP), HTTP/SSE-транспорт
diff --git a/src/Kompas.Mcp.Core/Modeling/PartModeler.cs b/src/Kompas.Mcp.Core/Modeling/PartModeler.cs
index 3e51771..b66c2ea 100644
--- a/src/Kompas.Mcp.Core/Modeling/PartModeler.cs
+++ b/src/Kompas.Mcp.Core/Modeling/PartModeler.cs
@@ -71,6 +71,25 @@ public sealed class PartModeler : IDisposable
return CreateSketchOn(part, face);
}, ct);
+ ///
+ /// Создать эскиз на грани детали, заданной индексом из list_faces, и открыть на редактирование.
+ /// Индекс стабилен, пока геометрия не меняется. Возвращает id эскиза.
+ ///
+ public Task
OpenSketchOnFaceIndexAsync(int faceIndex, CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() =>
+ {
+ var part = GetTopPart();
+ var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
+ ?? throw new InvalidOperationException("Не удалось получить коллекцию граней.");
+ if (faceIndex < 0 || faceIndex >= faces.GetCount())
+ throw new ArgumentOutOfRangeException(nameof(faceIndex),
+ $"Индекс грани вне диапазона [0; {faces.GetCount() - 1}]. Сверьтесь с list_faces.");
+
+ var face = faces.GetByIndex(faceIndex) as ksEntity
+ ?? throw new InvalidOperationException("Грань по индексу не приводится к ksEntity.");
+ return CreateSketchOn(part, face);
+ }, ct);
+
/// Общее ядро: создать эскиз на заданной плоскости/грани и открыть на редактирование.
private int CreateSketchOn(ksPart part, ksEntity planeEntity)
{
diff --git a/src/Kompas.Mcp.Core/Query/FaceInfo.cs b/src/Kompas.Mcp.Core/Query/FaceInfo.cs
new file mode 100644
index 0000000..22a7b4c
--- /dev/null
+++ b/src/Kompas.Mcp.Core/Query/FaceInfo.cs
@@ -0,0 +1,14 @@
+namespace Kompas.Mcp.Core.Query;
+
+/// Сводка по грани детали для выбора в итеративном цикле построения.
+public sealed record FaceInfo
+{
+ /// Индекс грани в коллекции (стабилен, пока геометрия не меняется).
+ public required int Index { get; init; }
+
+ /// Тип поверхности: plane, cylinder, cone, sphere, torus, nurbs, other.
+ public required string Type { get; init; }
+
+ /// Площадь грани, мм².
+ public required double Area { get; init; }
+}
diff --git a/src/Kompas.Mcp.Core/Query/QueryService.cs b/src/Kompas.Mcp.Core/Query/QueryService.cs
index 734034a..3f7272f 100644
--- a/src/Kompas.Mcp.Core/Query/QueryService.cs
+++ b/src/Kompas.Mcp.Core/Query/QueryService.cs
@@ -61,6 +61,54 @@ public sealed class QueryService
}
}, ct);
+ // ST_MIX_MM (0x1) — площади граней в мм².
+ private const uint MixMm = 0x1;
+
+ /// Перечислить грани активной детали: индекс, тип поверхности и площадь (мм²).
+ public Task> ListFacesAsync(CancellationToken ct = default)
+ => _dispatcher.InvokeAsync>(() =>
+ {
+ var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
+ ?? throw new InvalidOperationException("Нет активного 3D-документа.");
+ var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
+ ?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
+ try
+ {
+ var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
+ ?? throw new InvalidOperationException("Не удалось получить коллекцию граней.");
+
+ var count = faces.GetCount();
+ var list = new List(count);
+ for (int i = 0; i < count; i++)
+ {
+ if (faces.GetByIndex(i) is not ksEntity entity) continue;
+ if (entity.GetDefinition() is not ksFaceDefinition def) continue;
+ list.Add(new FaceInfo
+ {
+ Index = i,
+ Type = FaceType(def),
+ Area = def.GetArea(MixMm),
+ });
+ }
+ return list;
+ }
+ finally
+ {
+ if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
+ }
+ }, ct);
+
+ private static string FaceType(ksFaceDefinition def)
+ {
+ if (def.IsPlanar()) return "plane";
+ if (def.IsCylinder()) return "cylinder";
+ if (def.IsCone()) return "cone";
+ if (def.IsSphere()) return "sphere";
+ if (def.IsTorus()) return "torus";
+ if (def.IsNurbsSurface()) return "nurbs";
+ return "other";
+ }
+
/// Габаритный параллелепипед активной детали (по телам), мм.
public Task GetBoundingBoxAsync(CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
diff --git a/src/Kompas.Mcp.Host/Tools/QueryTools.cs b/src/Kompas.Mcp.Host/Tools/QueryTools.cs
index a24663c..121ac7c 100644
--- a/src/Kompas.Mcp.Host/Tools/QueryTools.cs
+++ b/src/Kompas.Mcp.Host/Tools/QueryTools.cs
@@ -19,6 +19,17 @@ public sealed class QueryTools(KompasSession session, QueryService query)
$"центр масс: ({i.CenterX:F2}, {i.CenterY:F2}, {i.CenterZ:F2}).";
}
+ [McpServerTool(Name = "list_faces")]
+ [Description("Перечислить грани активной детали: индекс, тип поверхности (plane/cylinder/cone/sphere/torus/nurbs) и площадь (мм²). Индекс используйте в sketch_create_on_face_index. Удобно для надёжного выбора грани без угадывания координат.")]
+ public async Task ListFaces()
+ {
+ await session.ConnectAsync();
+ var faces = await query.ListFacesAsync();
+ if (faces.Count == 0) return "Граней нет (модель пуста?).";
+ var lines = faces.Select(f => $" [{f.Index}] {f.Type}, площадь {f.Area:F2} мм²");
+ return $"Граней: {faces.Count}\n" + string.Join("\n", lines);
+ }
+
[McpServerTool(Name = "get_bounding_box")]
[Description("Габариты активной детали: координаты габаритного параллелепипеда (мм) и его размеры. Помогает оценить протяжённость модели и подобрать точки для выбора граней/рёбер.")]
public async Task GetBoundingBox()
diff --git a/src/Kompas.Mcp.Host/Tools/SketchTools.cs b/src/Kompas.Mcp.Host/Tools/SketchTools.cs
index 5895405..fdc3b03 100644
--- a/src/Kompas.Mcp.Host/Tools/SketchTools.cs
+++ b/src/Kompas.Mcp.Host/Tools/SketchTools.cs
@@ -45,6 +45,15 @@ public sealed class SketchTools(KompasSession session, PartModeler modeler)
return "Отрезок добавлен.";
}
+ [McpServerTool(Name = "sketch_create_on_face_index")]
+ [Description("Создать эскиз на грани детали, заданной индексом из list_faces, и открыть на редактирование. Надёжнее выбора по точке (не нужно угадывать координаты). Возвращает id эскиза.")]
+ public async Task CreateOnFaceIndex([Description("Индекс грани из list_faces")] int faceIndex)
+ {
+ await session.ConnectAsync();
+ var id = await modeler.OpenSketchOnFaceIndexAsync(faceIndex);
+ return $"Эскиз создан на грани [{faceIndex}], id={id}.";
+ }
+
[McpServerTool(Name = "sketch_add_axis")]
[Description("Добавить осевую линию (ось вращения) в открытый эскиз — нужна для revolve_boss/revolve_cut. Две точки в плоскости эскиза, мм.")]
public async Task AddAxis(int sketchId, double x1, double y1, double x2, double y2)
diff --git a/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs b/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs
index 7698fee..4c35661 100644
--- a/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs
+++ b/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs
@@ -155,6 +155,46 @@ public sealed class ModelingTests
}
}
+ [Fact]
+ public async Task List_faces_and_sketch_by_index()
+ {
+ await _docs.CreateAsync(KompasDocumentType.Part);
+ try
+ {
+ // Цилиндр R10×H20: 3 грани — 2 плоские (торцы) + 1 цилиндрическая (бок).
+ var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
+ await _modeler.AddCircleAsync(s1, 0, 0, 10);
+ await _modeler.CloseSketchAsync(s1);
+ await _modeler.ExtrudeAsync(s1, depth: 20);
+
+ var faces = await _query.ListFacesAsync();
+ Assert.Equal(3, faces.Count);
+ Assert.Equal(2, faces.Count(f => f.Type == "plane"));
+ Assert.Equal(1, faces.Count(f => f.Type == "cylinder"));
+
+ // Площадь торца ≈ π·10² ≈ 314 мм²; боковой ≈ 2π·10·20 ≈ 1257 мм².
+ var planar = faces.First(f => f.Type == "plane");
+ Assert.InRange(planar.Area, Math.PI * 100 * 0.95, Math.PI * 100 * 1.05);
+ var side = faces.First(f => f.Type == "cylinder");
+ Assert.InRange(side.Area, 2 * Math.PI * 10 * 20 * 0.95, 2 * Math.PI * 10 * 20 * 1.05);
+
+ // Эскиз на плоской грани по индексу → бобышка (проверяем, что выбор по индексу работает).
+ var s2 = await _modeler.OpenSketchOnFaceIndexAsync(planar.Index);
+ await _modeler.AddCircleAsync(s2, 0, 0, 4);
+ await _modeler.CloseSketchAsync(s2);
+ var boss = await _modeler.ExtrudeAsync(s2, depth: 8, forward: false);
+ Assert.True(boss > 0);
+ await _modeler.RebuildAsync();
+
+ // Модель осталась валидной и опрашиваемой.
+ Assert.True((await _query.GetPartInfoAsync()).Volume > 0);
+ }
+ finally
+ {
+ await _docs.CloseAsync(save: false);
+ }
+ }
+
[Fact]
public async Task Reset_invalidates_sketch_ids()
{