diff --git a/docs/OPEN_QUESTIONS.md b/docs/OPEN_QUESTIONS.md index c221ef7..2cd4000 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -69,6 +69,16 @@ stateful-сессии? Пока полагаемся на последовате передавать через `ImageContentBlock.FromBytes(bytes, mimeType)` — он сам кодирует. Прямое присваивание `Data = rawBytes` портит изображение на проводе. Исправлено в VisionTools. +### ✅ МЦХ (get_part_info): единицы измерения и поведение Calculate() +`IMassInertiaParam7` по умолчанию возвращает значения в **единицах отображения МЦХ** документа +(по умолчанию см³/см²/г, плотность г/см³) — то есть зависит от настроек, а не фиксированные СИ. +Поэтому `QueryService` принудительно выставляет `LengthUnits = ksLUnMM` и `MassUnits = ksMUnKG` +перед чтением → детерминированные **мм³, мм², кг, мм**. Проверено тестом (цилиндр R10×H20: +V≈6283 мм³, S≈1885 мм², m≈0.0494 кг, Zc=10 мм). +Тонкость: `Calculate()` возвращает **FALSE даже при успехе** (когда расчёт уже актуален после +`RebuildDocument`), поэтому опираемся на свойство `Actual`, а не на возвращаемое значение. +Материал по умолчанию — сталь (ρ≈7.856 г/см³); масса зависит от назначенного материала. + ### ✅ Снимок 3D: рендер через файл, а не resultArrayBytes `ksDocument3D.SaveAsToRasterFormat(fileName, param)` при **непустом** `fileName` возвращает TRUE, пишет корректный PNG на диск, но `param.resultArrayBytes` остаётся **пустым**. Поэтому diff --git a/docs/presentation.html b/docs/presentation.html index 6a8c669..0676fcd 100644 --- a/docs/presentation.html +++ b/docs/presentation.html @@ -125,7 +125,7 @@
- Статус: v1 работает — цикл эскиз→выдавливание→снимок через MCP (18 инструментов) + Статус: v1 работает — цикл эскиз→выдавливание→снимок через MCP (19 инструментов)

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

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

-
Общий прогрессv1 работает · 12 тестов зелёные · доки и ревью
-
+
Общий прогрессv1 работает · 14 тестов зелёные · доки и ревью
+
@@ -282,14 +282,15 @@

Инструменты v1 ГОТОВО

-

18 инструментов, отдаются по MCP-протоколу

+

19 инструментов, отдаются по MCP-протоколу

  • System: connect · status · set_visible
  • Documents: create · open · save · save_as · close · active
  • Sketch: create · add line/circle/rectangle · close
  • Features: extrude_boss · extrude_cut · rebuild
  • Vision: model_snapshot 👁️
  • -
  • Query: select_face · list_bodies · get_part_info (далее)
  • +
  • Query: get_part_info (объём/масса/площадь/центр масс)
  • +
  • Query: select_face · list_bodies (далее)
diff --git a/src/Kompas.Mcp.Core/Query/PartInfo.cs b/src/Kompas.Mcp.Core/Query/PartInfo.cs new file mode 100644 index 0000000..8658cb7 --- /dev/null +++ b/src/Kompas.Mcp.Core/Query/PartInfo.cs @@ -0,0 +1,12 @@ +namespace Kompas.Mcp.Core.Query; + +/// Массово-центровочные характеристики детали (в единицах документа, по умолчанию мм). +public sealed record PartInfo +{ + public required double Volume { get; init; } // объём, мм³ + public required double Mass { get; init; } // масса, кг (по материалу/плотности) + public required double Area { get; init; } // площадь поверхности, мм² + public required double CenterX { get; init; } // центр масс, X + public required double CenterY { get; init; } + public required double CenterZ { get; init; } +} diff --git a/src/Kompas.Mcp.Core/Query/QueryService.cs b/src/Kompas.Mcp.Core/Query/QueryService.cs new file mode 100644 index 0000000..6c9bce6 --- /dev/null +++ b/src/Kompas.Mcp.Core/Query/QueryService.cs @@ -0,0 +1,61 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Kompas.Mcp.Core.Threading; +using Kompas6Constants; +using KompasAPI7; + +namespace Kompas.Mcp.Core.Query; + +/// Запросы к активной 3D-модели: массово-центровочные характеристики. +[SupportedOSPlatform("windows")] +public sealed class QueryService +{ + private readonly KompasSession _session; + private readonly KompasDispatcher _dispatcher; + + public QueryService(KompasSession session, KompasDispatcher dispatcher) + { + _session = session ?? throw new ArgumentNullException(nameof(session)); + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + } + + /// Рассчитать и вернуть МЦХ активной детали (объём, масса, площадь, центр масс). + public Task GetPartInfoAsync(CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + { + var doc3d = _session.Application.ActiveDocument as IKompasDocument3D + ?? throw new InvalidOperationException("Активный документ не является 3D-моделью."); + var part = doc3d.TopPart + ?? throw new InvalidOperationException("Не удалось получить вершинный компонент."); + try + { + var mass = (IMassInertiaParam7)part; + + // Детерминированные единицы: длина — мм, масса — кг. Иначе КОМПАС вернёт + // значения в единицах отображения МЦХ (по умолчанию см/г), что зависит от настроек. + mass.LengthUnits = ksLengthUnitsEnum.ksLUnMM; + mass.MassUnits = ksMassUnitsEnum.ksMUnKG; + + // Calculate() возвращает FALSE и при успехе (когда расчёт уже актуален), + // поэтому ориентируемся на свойство Actual, а не на возвращаемое значение. + mass.Calculate(); + if (!mass.Actual) + throw new InvalidOperationException("МЦХ не рассчитаны (модель пуста или не перестроена?)."); + + return new PartInfo + { + Volume = mass.Volume, + Mass = mass.Mass, + Area = mass.Area, + CenterX = mass.Xc, + CenterY = mass.Yc, + CenterZ = mass.Zc, + }; + } + finally + { + // part и его QI-приведение mass — один RCW; release один раз. + if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part); + } + }, ct); +} diff --git a/src/Kompas.Mcp.Host/Program.cs b/src/Kompas.Mcp.Host/Program.cs index 3ed0fee..cd297da 100644 --- a/src/Kompas.Mcp.Host/Program.cs +++ b/src/Kompas.Mcp.Host/Program.cs @@ -1,6 +1,7 @@ using Kompas.Mcp.Core; using Kompas.Mcp.Core.Documents; using Kompas.Mcp.Core.Modeling; +using Kompas.Mcp.Core.Query; using Kompas.Mcp.Core.Threading; using Kompas.Mcp.Core.Vision; using Microsoft.Extensions.DependencyInjection; @@ -19,6 +20,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // MCP-сервер поверх stdio; инструменты находятся по атрибутам в этой сборке. builder.Services diff --git a/src/Kompas.Mcp.Host/Tools/QueryTools.cs b/src/Kompas.Mcp.Host/Tools/QueryTools.cs new file mode 100644 index 0000000..2cc4b3f --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/QueryTools.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Query; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Инструменты запросов к модели. +[McpServerToolType] +public sealed class QueryTools(KompasSession session, QueryService query) +{ + [McpServerTool(Name = "get_part_info")] + [Description("Массово-центровочные характеристики активной детали: объём (мм³), масса (кг), площадь (мм²) и центр масс. Полезно для числовой проверки построения.")] + public async Task GetPartInfo() + { + await session.ConnectAsync(); + var i = await query.GetPartInfoAsync(); + return $"Объём: {i.Volume:F2} мм³; масса: {i.Mass:F4} кг; площадь: {i.Area:F2} мм²; " + + $"центр масс: ({i.CenterX:F2}, {i.CenterY:F2}, {i.CenterZ:F2})."; + } +} diff --git a/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs b/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs new file mode 100644 index 0000000..942da2d --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs @@ -0,0 +1,60 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Modeling; +using Kompas.Mcp.Core.Query; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: массово-центровочные характеристики (get_part_info). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class QueryTests +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly QueryService _query; + + public QueryTests(KompasFixture fx) + { + _docs = new DocumentService(fx.Session, fx.Dispatcher); + _modeler = new PartModeler(fx.Session, fx.Dispatcher); + _query = new QueryService(fx.Session, fx.Dispatcher); + } + + [Fact] + public async Task Cylinder_volume_matches_formula() + { + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + // Цилиндр R10 × H20 на XOY: объём = π·R²·H = π·100·20 ≈ 6283.19 мм³. + var s = await _modeler.OpenSketchAsync(BasePlane.XOY); + await _modeler.AddCircleAsync(s, 0, 0, 10); + await _modeler.CloseSketchAsync(s); + var boss = await _modeler.ExtrudeAsync(s, depth: 20); + Assert.True(boss > 0); + await _modeler.RebuildAsync(); + + var info = await _query.GetPartInfoAsync(); + + // Единицы детерминированы сервисом: мм³, мм², кг, мм. + var expectedVolume = Math.PI * 100 * 20; // ≈ 6283.19 мм³ + Assert.InRange(info.Volume, expectedVolume * 0.99, expectedVolume * 1.01); + + // Центр масс цилиндра — на оси, по высоте посередине (Z = H/2 = 10 мм). + Assert.InRange(info.CenterX, -0.1, 0.1); + Assert.InRange(info.CenterY, -0.1, 0.1); + Assert.InRange(info.CenterZ, 9.9, 10.1); + + // Площадь поверхности: 2·π·R² (торцы) + 2·π·R·H (бок) = 2π·100 + 2π·10·20 ≈ 1884.96 мм². + var expectedArea = 2 * Math.PI * 100 + 2 * Math.PI * 10 * 20; + Assert.InRange(info.Area, expectedArea * 0.98, expectedArea * 1.02); + + // Масса стального цилиндра (плотность ≈ 7.856 г/см³): V[см³]·ρ ≈ 6.283·7.856 ≈ 49.36 г = 0.0494 кг. + Assert.InRange(info.Mass, 0.045, 0.055); + } + finally + { + await _docs.CloseAsync(save: false); + } + } +}