From 8ac92095b6fe19f93af52a821aa44912325f3eb4 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Tue, 26 May 2026 10:08:20 +0300 Subject: [PATCH] =?UTF-8?q?@=20feat(query):=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D1=87=D0=B8=D1=81=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=B9=20+=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=20=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=20=D0=BF=D0=BE=20=D0=B8=D0=BD?= =?UTF-8?q?=D0=B4=D0=B5=D0=BA=D1=81=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QueryService.ListFacesAsync: обход EntityCollection(o3d_face), для каждой грани — индекс, тип поверхности (plane/cylinder/cone/sphere/torus/nurbs через ksFaceDefinition.Is*) и площадь (GetArea, мм²). Запись FaceInfo. PartModeler.OpenSketchOnFaceIndexAsync: эскиз на грани по индексу из list_faces — надёжнее выбора по точке (агенту не нужно угадывать координаты). Новые инструменты: list_faces, sketch_create_on_face_index. Тест List_faces_and_sketch_by_index: цилиндр → 3 грани (2 plane + 1 cylinder), площади торца ≈314 мм² и бока ≈1257 мм²; эскиз на торце по индексу → бобышка. Презентация: 28 инструментов, 18 тестов. Co-Authored-By: Claude Opus 4.7 @ --- docs/presentation.html | 16 +++---- src/Kompas.Mcp.Core/Modeling/PartModeler.cs | 19 ++++++++ src/Kompas.Mcp.Core/Query/FaceInfo.cs | 14 ++++++ src/Kompas.Mcp.Core/Query/QueryService.cs | 48 +++++++++++++++++++ src/Kompas.Mcp.Host/Tools/QueryTools.cs | 11 +++++ src/Kompas.Mcp.Host/Tools/SketchTools.cs | 9 ++++ .../Integration/ModelingTests.cs | 40 ++++++++++++++++ 7 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 src/Kompas.Mcp.Core/Query/FaceInfo.cs diff --git a/docs/presentation.html b/docs/presentation.html index d12625b..0bacaec 100644 --- a/docs/presentation.html +++ b/docs/presentation.html @@ -125,7 +125,7 @@
- Статус: v1 работает — цикл эскиз→операции→снимок через MCP (26 инструментов) + Статус: v1 работает — цикл эскиз→операции→снимок через MCP (28 инструментов)

КОМПАС-3D MCP-сервер
управление CAD языком LLM

MCP-сервер, который превращает операции КОМПАС-3D — создание документов, эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом — @@ -246,8 +246,8 @@ впереди.

-
Общий прогрессv2: грань · вращение · скругление/фаска · 17 тестов зелёные
-
+
Общий прогрессv2: грань · вращение · скругление/фаска · габариты · список граней · 18 тестов
+
@@ -282,15 +282,15 @@

Инструменты 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 (далее)
@@ -319,7 +319,7 @@ черчению и сборкам.

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() {