diff --git a/src/Kompas.Mcp.Core/KompasSession.cs b/src/Kompas.Mcp.Core/KompasSession.cs index 4988da9..1859048 100644 --- a/src/Kompas.Mcp.Core/KompasSession.cs +++ b/src/Kompas.Mcp.Core/KompasSession.cs @@ -43,6 +43,13 @@ public sealed class KompasSession : IDisposable public bool IsConnected => _app is not null; + /// Сведения о последнем успешном подключении (null, пока не подключались). + public KompasConnectionInfo? LastInfo { get; private set; } + + /// Показать/скрыть окно КОМПАС. + public Task SetVisibleAsync(bool visible, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => Application.Visible = visible, ct); + /// Корневой объект API5. Доступен только на STA-потоке после подключения. public KompasObject Kompas => _kompas ?? throw new InvalidOperationException("Нет подключения к КОМПАС. Сначала вызовите ConnectAsync."); @@ -58,7 +65,7 @@ public sealed class KompasSession : IDisposable private KompasConnectionInfo ConnectCore(bool visible) { if (_app is not null) - return BuildInfo(LastProgId); + return LastInfo ??= BuildInfo(LastProgId); // 1) attach к уже запущенному экземпляру foreach (var id in ProgIds) @@ -107,7 +114,7 @@ public sealed class KompasSession : IDisposable _app.Visible = visible; - return BuildInfo(LastProgId); + return LastInfo = BuildInfo(LastProgId); } private string LastProgId { get; set; } = string.Empty; diff --git a/src/Kompas.Mcp.Host/Program.cs b/src/Kompas.Mcp.Host/Program.cs index 3751555..3ed0fee 100644 --- a/src/Kompas.Mcp.Host/Program.cs +++ b/src/Kompas.Mcp.Host/Program.cs @@ -1,2 +1,29 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Modeling; +using Kompas.Mcp.Core.Threading; +using Kompas.Mcp.Core.Vision; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = Host.CreateApplicationBuilder(args); + +// ВАЖНО: stdout занят протоколом MCP (stdio). Все логи — только в stderr. +builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace); +builder.Logging.SetMinimumLevel(LogLevel.Information); + +// COM-слой: один STA-поток, одно подключение, общие сервисы (синглтоны). +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +// MCP-сервер поверх stdio; инструменты находятся по атрибутам в этой сборке. +builder.Services + .AddMcpServer() + .WithStdioServerTransport() + .WithToolsFromAssembly(); + +await builder.Build().RunAsync(); diff --git a/src/Kompas.Mcp.Host/Tools/DocumentTools.cs b/src/Kompas.Mcp.Host/Tools/DocumentTools.cs new file mode 100644 index 0000000..39f1bf7 --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/DocumentTools.cs @@ -0,0 +1,69 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Documents; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Инструменты управления документами. +[McpServerToolType] +public sealed class DocumentTools(KompasSession session, DocumentService docs) +{ + [McpServerTool(Name = "document_create")] + [Description("Создать новый документ и сделать его активным. type: part | assembly | drawing | fragment | specification | text.")] + public async Task Create([Description("Тип документа")] string type) + { + await session.ConnectAsync(); + var info = await docs.CreateAsync(DocumentTypes.Parse(type)); + return $"Создан документ типа {info.Type}."; + } + + [McpServerTool(Name = "document_open")] + [Description("Открыть документ КОМПАС с диска по полному пути.")] + public async Task Open( + [Description("Полный путь к файлу")] string path, + [Description("Открыть только для чтения")] bool readOnly = false) + { + await session.ConnectAsync(); + var info = await docs.OpenAsync(path, readOnly); + return $"Открыт документ: {info.Name} ({info.Type})."; + } + + [McpServerTool(Name = "document_save_as")] + [Description("Сохранить активный документ по указанному пути. Безопасно для новых документов. Расширения: .m3d (деталь), .a3d (сборка), .cdw (чертёж), .frw (фрагмент).")] + public async Task SaveAs([Description("Полный путь файла с расширением")] string path) + { + await session.ConnectAsync(); + await docs.SaveAsAsync(path); + return $"Сохранено: {path}"; + } + + [McpServerTool(Name = "document_save")] + [Description("Сохранить активный документ (для уже сохранённого ранее).")] + public async Task Save() + { + await session.ConnectAsync(); + await docs.SaveAsync(); + return "Документ сохранён."; + } + + [McpServerTool(Name = "document_close")] + [Description("Закрыть активный документ. save — сохранять ли изменения при закрытии.")] + public async Task Close([Description("Сохранять изменения")] bool save = false) + { + await session.ConnectAsync(); + var ok = await docs.CloseAsync(save); + return ok ? "Документ закрыт." : "Не удалось закрыть документ."; + } + + [McpServerTool(Name = "document_active")] + [Description("Сведения об активном документе.")] + public async Task Active() + { + await session.ConnectAsync(); + var i = await docs.GetActiveAsync(); + if (i is null) return "Нет активного документа."; + var where = string.IsNullOrEmpty(i.PathName) ? "не сохранён" : i.PathName; + return $"{i.Type}: {i.Name} ({where})."; + } +} diff --git a/src/Kompas.Mcp.Host/Tools/FeatureTools.cs b/src/Kompas.Mcp.Host/Tools/FeatureTools.cs new file mode 100644 index 0000000..fa372f0 --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/FeatureTools.cs @@ -0,0 +1,42 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Modeling; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Инструменты формообразующих операций. +[McpServerToolType] +public sealed class FeatureTools(KompasSession session, PartModeler modeler) +{ + [McpServerTool(Name = "extrude_boss")] + [Description("Выдавить эскиз, добавив материал (на глубину depth, мм). Возвращает id операции.")] + public async Task ExtrudeBoss(int sketchId, double depth, [Description("Прямое направление")] bool forward = true) + { + await session.ConnectAsync(); + var id = await modeler.ExtrudeAsync(sketchId, depth, cut: false, forward: forward); + return $"Выдавливание (boss) создано, id={id}."; + } + + [McpServerTool(Name = "extrude_cut")] + [Description("Вырезать выдавливанием по эскизу. throughAll=true — насквозь (для отверстий, depth игнорируется); иначе на глубину depth.")] + public async Task ExtrudeCut( + int sketchId, + [Description("Глубина, мм (если не throughAll)")] double depth = 0, + [Description("Вырезать насквозь")] bool throughAll = true, + [Description("Прямое направление")] bool forward = true) + { + await session.ConnectAsync(); + var id = await modeler.ExtrudeAsync(sketchId, depth <= 0 ? 1 : depth, cut: true, forward: forward, throughAll: throughAll); + return $"Вырез (cut) создан, id={id}."; + } + + [McpServerTool(Name = "rebuild")] + [Description("Перестроить активный документ.")] + public async Task Rebuild() + { + await session.ConnectAsync(); + await modeler.RebuildAsync(); + return "Документ перестроен."; + } +} diff --git a/src/Kompas.Mcp.Host/Tools/SketchTools.cs b/src/Kompas.Mcp.Host/Tools/SketchTools.cs new file mode 100644 index 0000000..1f0b97b --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/SketchTools.cs @@ -0,0 +1,56 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Modeling; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Инструменты построения эскизов в активной детали. +[McpServerToolType] +public sealed class SketchTools(KompasSession session, PartModeler modeler) +{ + [McpServerTool(Name = "sketch_create")] + [Description("Создать эскиз на базовой плоскости (XOY, XOZ или YOZ) активной детали и открыть его на редактирование. Возвращает id эскиза для последующих sketch_add_* и операций.")] + public async Task Create([Description("Плоскость: XOY, XOZ или YOZ")] string plane) + { + await session.ConnectAsync(); + var id = await modeler.OpenSketchAsync(BasePlanes.Parse(plane)); + return $"Эскиз создан, id={id}. Добавьте геометрию (sketch_add_*) и завершите sketch_close."; + } + + [McpServerTool(Name = "sketch_add_circle")] + [Description("Добавить окружность в открытый эскиз. Координаты — в плоскости эскиза, мм.")] + public async Task AddCircle(int sketchId, double centerX, double centerY, double radius) + { + await session.ConnectAsync(); + await modeler.AddCircleAsync(sketchId, centerX, centerY, radius); + return "Окружность добавлена."; + } + + [McpServerTool(Name = "sketch_add_line")] + [Description("Добавить отрезок в открытый эскиз (две точки, мм).")] + public async Task AddLine(int sketchId, double x1, double y1, double x2, double y2) + { + await session.ConnectAsync(); + await modeler.AddLineAsync(sketchId, x1, y1, x2, y2); + return "Отрезок добавлен."; + } + + [McpServerTool(Name = "sketch_add_rectangle")] + [Description("Добавить прямоугольник по двум противоположным углам в открытый эскиз (мм).")] + public async Task AddRectangle(int sketchId, double x1, double y1, double x2, double y2) + { + await session.ConnectAsync(); + await modeler.AddRectangleAsync(sketchId, x1, y1, x2, y2); + return "Прямоугольник добавлен."; + } + + [McpServerTool(Name = "sketch_close")] + [Description("Завершить редактирование эскиза (перед операцией выдавливания).")] + public async Task Close(int sketchId) + { + await session.ConnectAsync(); + await modeler.CloseSketchAsync(sketchId); + return "Эскиз закрыт."; + } +} diff --git a/src/Kompas.Mcp.Host/Tools/SystemTools.cs b/src/Kompas.Mcp.Host/Tools/SystemTools.cs new file mode 100644 index 0000000..db069e4 --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/SystemTools.cs @@ -0,0 +1,34 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Системные инструменты: подключение, статус, видимость окна. +[McpServerToolType] +public sealed class SystemTools(KompasSession session) +{ + [McpServerTool(Name = "kompas_connect")] + [Description("Подключиться к КОМПАС-3D: присоединиться к запущенному экземпляру или запустить новый, показать окно. Возвращает версию и редакцию.")] + public async Task Connect() + { + var info = await session.ConnectAsync(); + return $"Подключено к КОМПАС {info.Version} (build {info.BuildNumber}, {info.Edition}), ProgID {info.ProgId}."; + } + + [McpServerTool(Name = "kompas_status")] + [Description("Текущее состояние подключения к КОМПАС.")] + public string Status() + => session.IsConnected && session.LastInfo is { } i + ? $"Подключено: КОМПАС {i.Version} ({i.Edition})." + : "Не подключено. Вызовите kompas_connect."; + + [McpServerTool(Name = "kompas_set_visible")] + [Description("Показать или скрыть главное окно КОМПАС.")] + public async Task SetVisible([Description("true — показать, false — скрыть")] bool visible) + { + await session.ConnectAsync(); + await session.SetVisibleAsync(visible); + return visible ? "Окно КОМПАС показано." : "Окно КОМПАС скрыто."; + } +} diff --git a/src/Kompas.Mcp.Host/Tools/VisionTools.cs b/src/Kompas.Mcp.Host/Tools/VisionTools.cs new file mode 100644 index 0000000..eb00409 --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/VisionTools.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Vision; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// «Зрение агента»: снимок 3D-модели. +[McpServerToolType] +public sealed class VisionTools(KompasSession session, SnapshotService snapshot) +{ + [McpServerTool(Name = "model_snapshot")] + [Description("Снять текущий вид активной 3D-модели в PNG и вернуть как изображение — чтобы увидеть результат построения и решить следующий шаг.")] + public async Task Snapshot( + [Description("Разрешение в DPI; 0 — экранное")] int resolution = 0) + { + await session.ConnectAsync(); + var snap = await snapshot.CaptureActiveModelAsync(resolution: resolution); + return new ImageContentBlock + { + Data = snap.Bytes, + MimeType = snap.MimeType, + }; + } +}