diff --git a/README.md b/README.md index 20070cf..466f49d 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,279 @@ -# revit-mcp-plugin +# 🔗 Revit MCP Plugin + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Revit 2019-2024](https://img.shields.io/badge/Revit-2019--2024-blue.svg)](https://www.autodesk.com/products/revit/overview) +[![Platform: Windows](https://img.shields.io/badge/Platform-Windows-lightgrey.svg)](https://www.microsoft.com/windows) + +> A powerful Revit plugin that enables AI assistants to interact with Autodesk Revit through the Model Context Protocol (MCP) + +## 📋 Table of Contents + +- [✨ Features](#-features) +- [📋 Prerequisites](#-prerequisites) +- [🚀 Installation](#-installation) +- [⚙️ Configuration](#️-configuration) +- [🎯 Usage](#-usage) +- [🔧 Custom Commands](#-custom-commands) +- [🏗️ Project Structure](#️-project-structure) +- [🐛 Troubleshooting](#-troubleshooting) +- [🤝 Contributing](#-contributing) +- [📄 License](#-license) + +## ✨ Features + +- **🤖 AI Integration**: Seamless integration with AI assistants via MCP protocol +- **⚡ Command Management**: Dynamic loading and management of Revit command sets +- **🔌 Socket Communication**: Real-time communication with external MCP services +- **🎛️ Settings Interface**: User-friendly configuration through Revit add-in interface +- **📦 Version Compatibility**: Support for Revit 2019-2024 +- **🔧 Extensible Architecture**: Easy development of custom commands + +## 📋 Prerequisites + +### System Requirements +- **Operating System**: Windows 10/11 (64-bit) +- **Revit Version**: 2019, 2020, 2021, 2022, 2023, or 2024 +- **.NET Framework**: 4.8 or higher +- **Memory**: 8GB RAM minimum, 16GB recommended +- **Disk Space**: 500MB free space + +### Dependencies +This plugin works in conjunction with: +- **[revit-mcp](https://github.com/revit-mcp/revit-mcp)**: Core MCP service provider +- **[revit-mcp-commandset](https://github.com/revit-mcp/revit-mcp-commandset)**: Command implementations + +## 🚀 Installation + +### Step 1: Build the Plugin + +1. **Clone the repository**: + ```bash + git clone https://github.com/neelshah-starline/revit-mcp-plugin.git + cd revit-mcp-plugin + ``` + +2. **Build the solution**: + - Open `revit-mcp-plugin.sln` in Visual Studio + - Set build configuration to `Release` + - Build the solution (`Ctrl+Shift+B`) + +3. **Locate the compiled DLL**: + - Navigate to `revit-mcp-plugin\bin\Release\` + - Copy `revit-mcp-plugin.dll` to your desired location + +### Step 2: Register the Plugin + +1. **Create the add-in file** (`revit-mcp-plugin.addin`): + ```xml + + + + revit-mcp + %your_path%\revit-mcp-plugin.dll + revit_mcp_plugin.Core.Application + 090A4C8C-61DC-426D-87DF-E4BAE0F80EC1 + revit-mcp + + + + ``` + +2. **Install the add-in**: + - Copy the `.addin` file to one of these locations: + - **Current User**: `%APPDATA%\Autodesk\Revit\Addins\2024\` (or your Revit version) + - **All Users**: `%ALLUSERSPROFILE%\Autodesk\Revit\Addins\2024\` (or your Revit version) + - Replace `%your_path%` with the actual path to your compiled DLL + +3. **Restart Revit** to load the plugin + +## ⚙️ Configuration + +### Initial Setup + +1. **Access Settings**: + - Launch Revit + - Navigate to **Add-in Modules → Revit MCP Plugin → Settings** + +2. **Configure Command Sets**: + - Click **OpenCommandSetFolder** to open the command sets directory + - Place your command set folders in this location + +### Command Set Structure -English | [简体中文](README_zh.md) +A typical command set should follow this structure: -## Introduction +``` +MyCommandSet/ +├── 2019/ # Revit 2019 compatible DLL +├── 2020/ # Revit 2020 compatible DLL +├── 2021/ # Revit 2021 compatible DLL +├── 2022/ # Revit 2022 compatible DLL +├── 2023/ # Revit 2023 compatible DLL +├── 2024/ # Revit 2024 compatible DLL +└── command.json # Command configuration file +``` -revit-mcp-plugin is a Revit plugin based on the MCP protocol, enabling AI to interact with Revit. +3. **Enable Commands**: + - Check the boxes for commands you want to load + - Click **Save** to apply changes -This project is part of the revit-mcp project (receives messages, loads command sets, operates Revit), and needs to be used in conjunction with [revit-mcp](https://github.com/revit-mcp/revit-mcp) (provides tools to AI) and [revit-mcp-commandset](https://github.com/revit-mcp/revit-mcp-commandset) (specific feature implementations). +## 🎯 Usage -## Environment Requirements +### Enable MCP Service -- Revit 2019~2024 +1. **Start the Service**: + - Go to **Add-in → Revit MCP Plugin → Revit MCP Switch** + - Toggle the service **ON** -## Usage Instructions +2. **AI Integration**: + - Your AI assistant can now discover and control your Revit instance + - The plugin will appear as an available MCP server -### Register Plugin +### Important Notes -Register the plugin and restart Revit: +- **⚠️ Configuration Changes**: If you modify command configurations after enabling the service, restart Revit for changes to take effect +- **🔒 Service State**: The service state persists between Revit sessions +- **📊 Real-time Updates**: Command availability updates dynamically when service is active -```xml - - - - revit-mcp - %your_path%\revit-mcp-plugin.dll - revit_mcp_plugin.Core.Application - 090A4C8C-61DC-426D-87DF-E4BAE0F80EC1 - revit-mcp - https://github.com/revit-mcp/revit-mcp-plugin - - -``` +## 🔧 Custom Commands -`%your_path%` needs to be replaced with the actual path after compilation. +### Development Overview -### Configure Commands +Custom commands extend the plugin's functionality. Refer to the [revit-mcp-commandset](https://github.com/revit-mcp/revit-mcp-commandset) project for detailed development guidelines. -Add-in Modules -> Revit MCP Plugin -> Settings +### Quick Start -This interface is used to configure the commands to be loaded into Revit. Click OpenCommandSetFolder to open the folder storing command sets. A typical command set folder structure looks like this: +1. **Create Command Class**: + ```csharp + public class MyCustomCommand : RevitCommandBase + { + public override void Execute(ExternalCommandData commandData) + { + // Your command implementation + } + } + ``` + +2. **Register Command**: + - Implement `IExternalCommand` interface + - Add command configuration to `command.json` + - Build for target Revit versions + +## 🏗️ Project Structure ``` -CommandSetName/ -├── 2019/ # Compatible executable files for different versions -├── 2020/ -├── 2021/ -├── 2022/ -├── 2023/ -├── 2024/ -└── command.json # Configuration file +revit-mcp-plugin/ +├── Configuration/ # Configuration management +│ ├── CommandConfig.cs # Command configuration models +│ ├── ConfigurationManager.cs # Configuration loading/saving +│ ├── DeveloperInfo.cs # Developer information +│ ├── FrameworkConfig.cs # Framework settings +│ └── ServiceSettings.cs # Service configuration +│ +├── Core/ # Core functionality +│ ├── Application.cs # Plugin entry point +│ ├── CommandExecutor.cs # Command execution engine +│ ├── CommandManager.cs # Command lifecycle management +│ ├── ExternalEventManager.cs # Revit external events +│ ├── MCPServiceConnection.cs # MCP protocol handling +│ ├── RevitCommandRegistry.cs # Command registration +│ ├── Settings.cs # Settings interface trigger +│ └── SocketService.cs # Network communication +│ +├── UI/ # User interface components +│ ├── SettingsWindow.xaml # Main settings window +│ ├── SettingsWindow.xaml.cs # Settings window logic +│ └── CommandSetSettingsPage.xaml # Command configuration UI +│ +└── Utils/ # Utility classes + ├── Logger.cs # Logging functionality + └── PathManager.cs # Path management utilities ``` -Successfully identified commands need to be checked to be loaded and used. +## 🐛 Troubleshooting -### Enable Service +### Common Issues -Add-in -> Revit MCP Plugin -> Revit MCP Switch +**❌ Plugin doesn't load** +- Verify the `.addin` file is in the correct location +- Check that the DLL path in the `.addin` file is correct +- Ensure Revit version compatibility +- Check Windows Event Viewer for error details -Open the service to allow AI to discover your Revit program. Now AI can control your Revit! +**❌ Commands don't appear** +- Verify command set folder structure +- Check `command.json` syntax +- Ensure compatible DLLs are present for your Revit version +- Restart Revit after configuration changes -> Note: If you modify the configured commands after enabling the service, you may need to restart REVIT for the configuration to take effect. This is related to whether the command has already been registered. +**❌ MCP service not discovered** +- Confirm the service is enabled in Revit +- Check firewall settings for network communication +- Verify MCP client configuration -## Custom Commands +**❌ Command execution fails** +- Check Revit API compatibility +- Verify command permissions +- Review Revit Journal files for errors +- Enable logging for detailed error information -You can refer to the [revit-mcp-commandset](https://github.com/revit-mcp/revit-mcp-commandset) project to develop custom commands. +### Getting Help -## Project File Organization +1. **Enable Logging**: + - Check **Enable Logging** in settings + - Review log files in the plugin directory -``` -revit-mcp-plugin/ -├── Configuration/ # Configuration management related classes -│ ├── CommandConfig.cs # Command configuration -│ ├── ConfigurationManager.cs # Configuration manager -│ ├── DeveloperInfo.cs # Developer information -│ ├── FrameworkConfig.cs # Framework configuration -│ └── ServiceSettings.cs # Service settings -│ -├── Core/ # Program entry and core functionality -│ ├── Application.cs # Application entry point -│ ├── CommandExecutor.cs # Command executor -│ ├── CommandManager.cs # Command manager -│ ├── ExternalEventManager.cs # External event manager -│ ├── MCPServiceConnection.cs # MCP service connection -│ ├── RevitCommandRegistry.cs # Revit command registration -│ ├── Settings.cs # Application settings -│ └── SocketService.cs # Socket service implementation -│ -├── Models/ # Data models -│ └── ... # Various data model classes -│ -├── UI/ # WPF form interfaces -│ └── ... # Interface related classes -│ -└── Utils/ # Utility classes - ├── Logger.cs # Logging utility - └── PathManager.cs # Path management utility -``` +2. **Check Revit Journal**: + - Located in `%APPDATA%\Autodesk\Revit\Journals\` + - Look for errors related to the plugin + +3. **Community Support**: + - [GitHub Issues](https://github.com/neelshah-starline/revit-mcp-plugin/issues) + - [Discussions](https://github.com/neelshah-starline/revit-mcp-plugin/discussions) + +## 🤝 Contributing + +We welcome contributions! Here's how you can help: + +### Development Setup + +1. **Fork the repository** +2. **Create a feature branch**: `git checkout -b feature/amazing-feature` +3. **Make your changes** +4. **Add tests** for new functionality +5. **Ensure all tests pass** +6. **Commit your changes**: `git commit -m 'Add amazing feature'` +7. **Push to branch**: `git push origin feature/amazing-feature` +8. **Open a Pull Request** + +### Contribution Guidelines + +- **📝 Code Style**: Follow existing code patterns and conventions +- **🧪 Testing**: Add tests for new features +- **📚 Documentation**: Update documentation for API changes +- **🔄 Compatibility**: Maintain backward compatibility when possible +- **🐛 Bug Reports**: Use GitHub Issues with detailed information + +### Areas for Contribution -### Configuration Directory -Responsible for managing various configuration information for the plugin: +- 🔧 Bug fixes and performance improvements +- ✨ New features and command implementations +- 📚 Documentation and usage examples +- 🧪 Test coverage expansion +- 🌐 Localization support -- CommandConfig.cs: Defines command-related configuration -- ConfigurationManager.cs: Manages loading, saving, and accessing configurations -- DeveloperInfo.cs: Stores developer-related information -- FrameworkConfig.cs: Framework-level configuration settings -- ServiceSettings.cs: Service-related settings +## 📄 License -### Core Directory -Contains the core functionality and entry point of the plugin: +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details. -- Application.cs: Application entry point, responsible for initializing the plugin -- CommandExecutor.cs: Core component responsible for executing Revit commands -- CommandManager.cs: Manages and dispatches various commands in the plugin -- ExternalEventManager.cs: Manages Revit external events -- MCPServiceConnection.cs: MCP service connection -- RevitCommandRegistry.cs: Registers and manages available Revit commands -- Settings.cs: Triggers the display of the settings interface -- SocketService.cs: Implements Socket communication with external clients +--- -### Models Directory -Contains data model classes used to pass data between different parts of the system. +
-### UI Directory -Contains user interface related components of the plugin, implemented using the WPF framework. +**Built with ❤️ for the Revit and AI community** -### Utils Directory -Provides various auxiliary tools: +[⭐ Star this project](https://github.com/neelshah-starline/revit-mcp-plugin) | +[🐛 Report issues](https://github.com/neelshah-starline/revit-mcp-plugin/issues) | +[💬 Start discussion](https://github.com/neelshah-starline/revit-mcp-plugin/discussions) -- Logger.cs: Logging tool for debugging and error tracking -- PathManager.cs: Project-related file path management +
diff --git a/README_zh.md b/README_zh.md deleted file mode 100644 index 8a953c2..0000000 --- a/README_zh.md +++ /dev/null @@ -1,131 +0,0 @@ -# revit-mcp-plugin - -[English](README.md) | 简体中文 - -## 简介 - -revit-mcp-plugin 是一个revit插件,基于 MCP 协议制作,从而使AI可以对 Revit 进行交互。 - -本项目是revit-mcp项目中的一部分(接收信息,装载功能集,操作revit),还需要配合[revit-mcp](https://github.com/revit-mcp/revit-mcp)(向AI提供tools)以及[revit-mcp-commandset](https://github.com/revit-mcp/revit-mcp-commandset)(具体的功能实现)使用。 - -## 环境要求 - -- revit 2019~2024 - -## 使用方法 - -### 注册插件 - -注册插件,重启Revit - -``` - - - - revit-mcp - %your_path%\revit-mcp-plugin.dll - revit_mcp_plugin.Core.Application - 090A4C8C-61DC-426D-87DF-E4BAE0F80EC1 - revit-mcp - https://github.com/revit-mcp/revit-mcp-plugin - - -``` - -`%your_path%`需要替换为实际编译后的路径 - -### 配置命令 - -附加模块->Revit MCP Plugin->Settings - -这个界面用于配置需要装载到revit中的命令,点击OpenCommandSetFolder打开存放命令集的文件夹,一个典型的命令集文件夹结构是这样的 - -``` -命令集名称/ -├── 2019/ # 兼容不同版本的执行文件 -├── 2020/ -├── 2021/ -├── 2022/ -├── 2023/ -├── 2024/ -└── command.json # 配置文件 -``` - -成功被识别的命令被勾选后,才会被加载和使用 - -### 启用服务 - -附加模块->Revit MCP Plugin->Revit MCP Switch - -打开服务,让ai可以发现你的revit程序,现在ai可以操控你的revit了! - -> 注意:如果启用服务后,修改了配置的命令,可能需要重启REVIT才能使配置生效,这与是否命令已注册相关 - -## 自定义命令 - -可以参考[revit-mcp-commandset](https://github.com/revit-mcp/revit-mcp-commandset)项目,开发自定义命令 - -## 项目文件组织结构 - -``` -revit-mcp-plugin/ -├── Configuration/ # 配置管理相关类 -│ ├── CommandConfig.cs # 命令配置 -│ ├── ConfigurationManager.cs # 配置管理器 -│ ├── DeveloperInfo.cs # 开发者信息 -│ ├── FrameworkConfig.cs # 框架配置 -│ └── ServiceSettings.cs # 服务设置 -│ -├── Core/ # 程序入口和核心功能 -│ ├── Application.cs # 应用程序入口点 -│ ├── CommandExecutor.cs # 命令执行器 -│ ├── CommandManager.cs # 命令管理器 -│ ├── ExternalEventManager.cs # 外部事件管理器 -│ ├── MCPServiceConnection.cs # MCP服务连接 -│ ├── RevitCommandRegistry.cs # Revit命令注册 -│ ├── Settings.cs # 应用程序设置 -│ └── SocketService.cs # Socket服务实现 -│ -├── Models/ # 数据模型 -│ └── ... # 各种数据模型类 -│ -├── UI/ # WPF窗体界面 -│ └── ... # 界面相关类 -│ -└── Utils/ # 工具类 - ├── Logger.cs # 日志工具 - └── PathManager.cs # 路径管理工具 -``` - -### Configuration 目录 -负责管理插件的各种配置信息: - -- CommandConfig.cs: 定义命令相关配置 -- ConfigurationManager.cs: 管理配置的加载、保存和访问 -- DeveloperInfo.cs: 存储开发者相关信息 -- FrameworkConfig.cs: 框架级别的配置设置 -- ServiceSettings.cs: 服务相关设置 - -### Core 目录 -包含插件的核心功能和入口点: - -- Application.cs: 应用程序入口点,负责初始化插件 -- CommandExecutor.cs: 负责执行Revit命令的核心组件 -- CommandManager.cs: 管理和调度插件中的各种命令 -- ExternalEventManager.cs: 管理Revit外部事件 -- MCPServiceConnection.cs: MCP服务连接 -- RevitCommandRegistry.cs: 注册和管理可用的Revit命令 -- Settings.cs: 触发显示设置界面 -- SocketService.cs: 实现与外部客户端的Socket通信 - -### Models 目录 -包含数据模型类,用于在系统各部分之间传递数据。 - -### UI 目录 -包含插件的用户界面相关组件,使用WPF框架实现。 - -### Utils 目录 -提供各种辅助工具: - -- Logger.cs: 日志记录工具,用于调试和错误追踪 -- PathManager.cs: 项目相关文件路径管理 diff --git a/SampleCommandSet/Commands/Access/GetAvailableFamilyTypesCommand.cs b/SampleCommandSet/Commands/Access/GetAvailableFamilyTypesCommand.cs deleted file mode 100644 index 76d7c22..0000000 --- a/SampleCommandSet/Commands/Access/GetAvailableFamilyTypesCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Autodesk.Revit.UI; -using Newtonsoft.Json.Linq; -using revit_mcp_sdk.API.Base; -using System; -using System.Collections.Generic; - -namespace SampleCommandSet.Commands.Access -{ - /// - /// 获取当前项目中可用族类型的命令 - /// - public class GetAvailableFamilyTypesCommand : ExternalEventCommandBase - { - private GetAvailableFamilyTypesEventHandler _handler => (GetAvailableFamilyTypesEventHandler)Handler; - - public override string CommandName => "get_available_family_types"; - - public GetAvailableFamilyTypesCommand(UIApplication uiApp) - : base(new GetAvailableFamilyTypesEventHandler(), uiApp) - { - } - - public override object Execute(JObject parameters, string requestId) - { - try - { - // 解析参数 - List categoryList = parameters?["categoryList"]?.ToObject>() ?? new List(); - string familyNameFilter = parameters?["familyNameFilter"]?.Value(); - int? limit = parameters?["limit"]?.Value(); - - // 设置查询参数 - _handler.CategoryList = categoryList; - _handler.FamilyNameFilter = familyNameFilter; - _handler.Limit = limit; - - // 触发外部事件并等待完成,最多等待15秒 - if (RaiseAndWaitForCompletion(15000)) - { - return _handler.ResultFamilyTypes; - } - else - { - throw new TimeoutException("获取可用族类型超时"); - } - } - catch (Exception ex) - { - throw new Exception($"获取可用族类型失败: {ex.Message}"); - } - } - } -} \ No newline at end of file diff --git a/SampleCommandSet/Commands/Access/GetAvailableFamilyTypesEventHandler.cs b/SampleCommandSet/Commands/Access/GetAvailableFamilyTypesEventHandler.cs deleted file mode 100644 index fea70e9..0000000 --- a/SampleCommandSet/Commands/Access/GetAvailableFamilyTypesEventHandler.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Interfaces; -using SampleCommandSet.Extensions; -using SampleCommandSet.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace SampleCommandSet.Commands.Access -{ - public class GetAvailableFamilyTypesEventHandler : IExternalEventHandler, IWaitableExternalEventHandler - { - // 执行结果 - public List ResultFamilyTypes { get; private set; } - - // 状态同步对象 - public bool TaskCompleted { get; private set; } - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - - // 过滤条件 - public List CategoryList { get; set; } - public string FamilyNameFilter { get; set; } - public int? Limit { get; set; } - - // 执行时间,略微比调用超时更短一些 - public bool WaitForCompletion(int timeoutMilliseconds = 12500) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - - public void Execute(UIApplication app) - { - try - { - var doc = app.ActiveUIDocument.Document; - - // 获取所有族符号(可载入族) - var familySymbols = new FilteredElementCollector(doc) - .OfClass(typeof(FamilySymbol)) - .Cast(); - // 获取系统族类型(墙、楼板等) - var systemTypes = new List(); - systemTypes.AddRange(new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast()); - systemTypes.AddRange(new FilteredElementCollector(doc).OfClass(typeof(FloorType)).Cast()); - systemTypes.AddRange(new FilteredElementCollector(doc).OfClass(typeof(RoofType)).Cast()); - systemTypes.AddRange(new FilteredElementCollector(doc).OfClass(typeof(CurtainSystemType)).Cast()); - // 合并结果 - var allElements = familySymbols - .Cast() - .Concat(systemTypes) - .ToList(); - - IEnumerable filteredElements = allElements; - - // 类别过滤 - if (CategoryList != null && CategoryList.Any()) - { - var validCategoryIds = new List(); - foreach (var categoryName in CategoryList) - { - if (Enum.TryParse(categoryName, out BuiltInCategory bic)) - { - validCategoryIds.Add((int)bic); - } - } - - if (validCategoryIds.Any()) - { - filteredElements = filteredElements.Where(et => - { - var categoryId = et.Category?.Id.GetIdValue(); - return categoryId != null && validCategoryIds.Contains((int)categoryId.Value); - }); - } - } - - // 名称模糊匹配(同时匹配族名和类型名) - if (!string.IsNullOrEmpty(FamilyNameFilter)) - { - filteredElements = filteredElements.Where(et => - { - string familyName = (et is FamilySymbol fs) ? fs.FamilyName : et.get_Parameter( - BuiltInParameter.SYMBOL_FAMILY_NAME_PARAM)?.AsString() ?? ""; - - return (familyName?.IndexOf(FamilyNameFilter, StringComparison.OrdinalIgnoreCase) >= 0 || - et.Name.IndexOf(FamilyNameFilter, StringComparison.OrdinalIgnoreCase) >= 0); - }); - } - - // 限制返回数量 - if (Limit.HasValue && Limit.Value > 0) - { - filteredElements = filteredElements.Take(Limit.Value); - } - - // 转换为FamilyTypeInfo列表 - ResultFamilyTypes = filteredElements.Select(et => - { - string familyName; - if (et is FamilySymbol fs) - { - familyName = fs.FamilyName; - } - else - { - Parameter param = et.get_Parameter(BuiltInParameter.SYMBOL_FAMILY_NAME_PARAM); - familyName = param?.AsString() ?? et.GetType().Name.Replace("Type", ""); - } - return new FamilyTypeInfo - { - FamilyTypeId = et.Id.GetIdValue(), - UniqueId = et.UniqueId, - FamilyName = familyName, - TypeName = et.Name, - Category = et.Category?.Name - }; - }).ToList(); - } - catch (Exception ex) - { - TaskDialog.Show("Error", "获取族类型失败: " + ex.Message); - } - finally - { - TaskCompleted = true; - _resetEvent.Set(); - } - } - - public string GetName() - { - return "获取可用族类型"; - } - } -} \ No newline at end of file diff --git a/SampleCommandSet/Commands/Access/GetCurrentViewElementsCommand.cs b/SampleCommandSet/Commands/Access/GetCurrentViewElementsCommand.cs deleted file mode 100644 index 555b740..0000000 --- a/SampleCommandSet/Commands/Access/GetCurrentViewElementsCommand.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Autodesk.Revit.UI; -using Newtonsoft.Json.Linq; -using revit_mcp_sdk.API.Base; -using System; -using System.Collections.Generic; - -namespace SampleCommandSet.Commands.Access -{ - /// - /// 获取当前视图元素的命令 - /// - public class GetCurrentViewElementsCommand : ExternalEventCommandBase - { - private GetCurrentViewElementsEventHandler _handler => (GetCurrentViewElementsEventHandler)Handler; - - public override string CommandName => "get_current_view_elements"; - - public GetCurrentViewElementsCommand(UIApplication uiApp) - : base(new GetCurrentViewElementsEventHandler(), uiApp) - { - } - - public override object Execute(JObject parameters, string requestId) - { - try - { - // 解析参数 - List modelCategoryList = parameters?["modelCategoryList"]?.ToObject>() ?? new List(); - List annotationCategoryList = parameters?["annotationCategoryList"]?.ToObject>() ?? new List(); - bool includeHidden = parameters?["includeHidden"]?.Value() ?? false; - int limit = parameters?["limit"]?.Value() ?? 100; - - // 设置查询参数 - _handler.SetQueryParameters(modelCategoryList, annotationCategoryList, includeHidden, limit); - - // 触发外部事件并等待完成 - if (RaiseAndWaitForCompletion(60000)) // 60秒超时 - { - return _handler.ResultInfo; - } - else - { - throw new TimeoutException("获取视图元素超时"); - } - } - catch (Exception ex) - { - throw new Exception($"获取视图元素失败: {ex.Message}"); - } - } - } -} diff --git a/SampleCommandSet/Commands/Access/GetCurrentViewElementsEventHandler.cs b/SampleCommandSet/Commands/Access/GetCurrentViewElementsEventHandler.cs deleted file mode 100644 index 852c2cf..0000000 --- a/SampleCommandSet/Commands/Access/GetCurrentViewElementsEventHandler.cs +++ /dev/null @@ -1,248 +0,0 @@ -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using SampleCommandSet.Extensions; -using revit_mcp_sdk.API.Interfaces; - -namespace SampleCommandSet.Commands.Access -{ - /// - /// 获取当前视图元素的事件处理器 - /// - public class GetCurrentViewElementsEventHandler : IExternalEventHandler, IWaitableExternalEventHandler - { - // 默认模型类别列表 - private readonly List _defaultModelCategories = new List - { - "OST_Walls", - "OST_Doors", - "OST_Windows", - "OST_Furniture", - "OST_Columns", - "OST_Floors", - "OST_Roofs", - "OST_Stairs", - "OST_StructuralFraming", - "OST_Ceilings", - "OST_MEPSpaces", - "OST_Rooms" - }; - // 默认注释类别列表 - private readonly List _defaultAnnotationCategories = new List - { - "OST_Dimensions", - "OST_TextNotes", - "OST_GenericAnnotation", - "OST_WallTags", - "OST_DoorTags", - "OST_WindowTags", - "OST_RoomTags", - "OST_AreaTags", - "OST_SpaceTags", - "OST_ViewportLabels", - "OST_TitleBlocks" - }; - - // 查询参数 - private List _modelCategoryList; - private List _annotationCategoryList; - private bool _includeHidden; - private int _limit; - - // 执行结果 - public ViewElementsResult ResultInfo { get; private set; } - - // 状态同步对象 - public bool TaskCompleted { get; private set; } - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - - // 设置查询参数 - public void SetQueryParameters(List modelCategoryList, List annotationCategoryList, bool includeHidden, int limit) - { - _modelCategoryList = modelCategoryList; - _annotationCategoryList = annotationCategoryList; - _includeHidden = includeHidden; - _limit = limit; - TaskCompleted = false; - _resetEvent.Reset(); - } - - // 实现IWaitableExternalEventHandler接口 - public bool WaitForCompletion(int timeoutMilliseconds = 10000) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - - public void Execute(UIApplication app) - { - try - { - var uiDoc = app.ActiveUIDocument; - var doc = uiDoc.Document; - var activeView = doc.ActiveView; - - // 如果传入的类别列表为空,则使用默认列表 - List modelCategories = (_modelCategoryList == null || _modelCategoryList.Count == 0) - ? _defaultModelCategories - : _modelCategoryList; - - List annotationCategories = (_annotationCategoryList == null || _annotationCategoryList.Count == 0) - ? _defaultAnnotationCategories - : _annotationCategoryList; - - // 合并所有类别 - List allCategories = new List(); - allCategories.AddRange(modelCategories); - allCategories.AddRange(annotationCategories); - - // 获取当前视图中的所有元素 - var collector = new FilteredElementCollector(doc, activeView.Id) - .WhereElementIsNotElementType(); - - // 获取所有元素 - IList elements = collector.ToElements(); - - // 按类别筛选 - if (allCategories.Count > 0) - { - // 转换字符串类别为枚举 - List builtInCategories = new List(); - foreach (string categoryName in allCategories) - { - if (Enum.TryParse(categoryName, out BuiltInCategory category)) - { - builtInCategories.Add(category); - } - } - // 如果成功解析了类别,则使用类别过滤器 - if (builtInCategories.Count > 0) - { - ElementMulticategoryFilter categoryFilter = new ElementMulticategoryFilter(builtInCategories); - elements = new FilteredElementCollector(doc, activeView.Id) - .WhereElementIsNotElementType() - .WherePasses(categoryFilter) - .ToElements(); - } - } - - // 过滤隐藏的元素 - if (!_includeHidden) - { - elements = elements.Where(e => !e.IsHidden(activeView)).ToList(); - } - - // 限制返回数量 - if (_limit > 0 && elements.Count > _limit) - { - elements = elements.Take(_limit).ToList(); - } - - // 构建结果 - var elementInfos = elements.Select(e => new ElementInfo - { - Id = e.Id.GetIdValue(), - UniqueId = e.UniqueId, - Name = e.Name, - Category = e.Category?.Name ?? "unknow", - Properties = GetElementProperties(e) - }).ToList(); - - ResultInfo = new ViewElementsResult - { - ViewId = activeView.Id.GetIdValue(), - ViewName = activeView.Name, - TotalElementsInView = new FilteredElementCollector(doc, activeView.Id).GetElementCount(), - FilteredElementCount = elementInfos.Count, - Elements = elementInfos - }; - } - catch (Exception ex) - { - TaskDialog.Show("error", ex.Message); - } - finally - { - TaskCompleted = true; - _resetEvent.Set(); - } - } - - private Dictionary GetElementProperties(Element element) - { - var properties = new Dictionary(); - - // 添加通用属性 - properties.Add("ElementId", element.Id.GetIdValue().ToString()); - - if (element.Location != null) - { - if (element.Location is LocationPoint locationPoint) - { - var point = locationPoint.Point; - properties.Add("LocationX", point.X.ToString("F2")); - properties.Add("LocationY", point.Y.ToString("F2")); - properties.Add("LocationZ", point.Z.ToString("F2")); - } - else if (element.Location is LocationCurve locationCurve) - { - var curve = locationCurve.Curve; - properties.Add("Start", $"{curve.GetEndPoint(0).X:F2}, {curve.GetEndPoint(0).Y:F2}, {curve.GetEndPoint(0).Z:F2}"); - properties.Add("End", $"{curve.GetEndPoint(1).X:F2}, {curve.GetEndPoint(1).Y:F2}, {curve.GetEndPoint(1).Z:F2}"); - properties.Add("Length", curve.Length.ToString("F2")); - } - } - - // 获取常用参数值 - var commonParams = new[] { "Comments", "Mark", "Level", "Family", "Type" }; - foreach (var paramName in commonParams) - { - Parameter param = element.LookupParameter(paramName); - if (param != null && !param.IsReadOnly) - { - if (param.StorageType == StorageType.String) - properties.Add(paramName, param.AsString() ?? ""); - else if (param.StorageType == StorageType.Double) - properties.Add(paramName, param.AsDouble().ToString("F2")); - else if (param.StorageType == StorageType.Integer) - properties.Add(paramName, param.AsInteger().ToString()); - else if (param.StorageType == StorageType.ElementId) - properties.Add(paramName, param.AsElementId().GetIdValue().ToString()); - } - } - - return properties; - } - - public string GetName() - { - return "获取当前视图元素"; - } - } - - /// - /// 元素信息数据结构 - /// - public class ElementInfo - { - public long Id { get; set; } - public string UniqueId { get; set; } - public string Name { get; set; } - public string Category { get; set; } - public Dictionary Properties { get; set; } = new Dictionary(); - } - - /// - /// 视图元素结果数据结构 - /// - public class ViewElementsResult - { - public long ViewId { get; set; } - public string ViewName { get; set; } - public int TotalElementsInView { get; set; } - public int FilteredElementCount { get; set; } - public List Elements { get; set; } = new List(); - } -} diff --git a/SampleCommandSet/Commands/Access/GetCurrentViewInfoCommand.cs b/SampleCommandSet/Commands/Access/GetCurrentViewInfoCommand.cs deleted file mode 100644 index 46387f4..0000000 --- a/SampleCommandSet/Commands/Access/GetCurrentViewInfoCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Autodesk.Revit.UI; -using Newtonsoft.Json.Linq; -using revit_mcp_sdk.API.Base; -using System; - -namespace SampleCommandSet.Commands.Access -{ - public class GetCurrentViewInfoCommand : ExternalEventCommandBase - { - private GetCurrentViewInfoEventHandler _handler => (GetCurrentViewInfoEventHandler)Handler; - - public override string CommandName => "get_current_view_info"; - - public GetCurrentViewInfoCommand(UIApplication uiApp) - : base(new GetCurrentViewInfoEventHandler(), uiApp) - { - } - - public override object Execute(JObject parameters, string requestId) - { - // 触发外部事件并等待完成 - if (RaiseAndWaitForCompletion(10000)) // 10秒超时 - { - return _handler.ResultInfo; - } - else - { - throw new TimeoutException("获取信息超时"); - } - } - } - -} diff --git a/SampleCommandSet/Commands/Access/GetCurrentViewInfoEventHandler.cs b/SampleCommandSet/Commands/Access/GetCurrentViewInfoEventHandler.cs deleted file mode 100644 index bbe5457..0000000 --- a/SampleCommandSet/Commands/Access/GetCurrentViewInfoEventHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Interfaces; -using System; -using System.Threading; -using SampleCommandSet.Extensions; -using SampleCommandSet.Models; - -namespace SampleCommandSet.Commands.Access -{ - public class GetCurrentViewInfoEventHandler: IExternalEventHandler, IWaitableExternalEventHandler - { - // 执行结果 - public ViewInfo ResultInfo { get; private set; } - - // 状态同步对象 - public bool TaskCompleted { get; private set; } - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - - // 实现IWaitableExternalEventHandler接口 - public bool WaitForCompletion(int timeoutMilliseconds = 10000) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - - public void Execute(UIApplication app) - { - try - { - var uiDoc = app.ActiveUIDocument; - var doc = uiDoc.Document; - var activeView = doc.ActiveView; - - ResultInfo = new ViewInfo - { - Id = activeView.Id.GetIdValue(), - UniqueId = activeView.UniqueId, - Name = activeView.Name, - ViewType = activeView.ViewType.ToString(), - IsTemplate = activeView.IsTemplate, - Scale = activeView.Scale, - DetailLevel = activeView.DetailLevel.ToString(), - }; - } - catch (Exception ex) - { - TaskDialog.Show("error", "获取信息失败"); - } - finally - { - TaskCompleted = true; - _resetEvent.Set(); - } - } - - public string GetName() - { - return "获取当前视图信息"; - } - } -} diff --git a/SampleCommandSet/Commands/Access/GetSelectedElementsCommand.cs b/SampleCommandSet/Commands/Access/GetSelectedElementsCommand.cs deleted file mode 100644 index 6b5f6bd..0000000 --- a/SampleCommandSet/Commands/Access/GetSelectedElementsCommand.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Autodesk.Revit.UI; -using Newtonsoft.Json.Linq; -using revit_mcp_sdk.API.Base; -using System; - -namespace SampleCommandSet.Commands.Access -{ - /// - /// 获取当前选中元素的命令 - /// - public class GetSelectedElementsCommand : ExternalEventCommandBase - { - private GetSelectedElementsEventHandler _handler => (GetSelectedElementsEventHandler)Handler; - - public override string CommandName => "get_selected_elements"; - - public GetSelectedElementsCommand(UIApplication uiApp) - : base(new GetSelectedElementsEventHandler(), uiApp) - { - } - - public override object Execute(JObject parameters, string requestId) - { - try - { - // 解析参数 - int? limit = parameters?["limit"]?.Value(); - - // 设置数量限制 - _handler.Limit = limit; - - // 触发外部事件并等待完成 - if (RaiseAndWaitForCompletion(15000)) - { - return _handler.ResultElements; - } - else - { - throw new TimeoutException("获取选中元素超时"); - } - } - catch (Exception ex) - { - throw new Exception($"获取选中元素失败: {ex.Message}"); - } - } - } -} \ No newline at end of file diff --git a/SampleCommandSet/Commands/Access/GetSelectedElementsEventHandler.cs b/SampleCommandSet/Commands/Access/GetSelectedElementsEventHandler.cs deleted file mode 100644 index 4fb6dd7..0000000 --- a/SampleCommandSet/Commands/Access/GetSelectedElementsEventHandler.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using SampleCommandSet.Extensions; - -namespace SampleCommandSet.Commands.Access -{ - public class GetSelectedElementsEventHandler : IExternalEventHandler, IWaitableExternalEventHandler - { - // 执行结果 - public List ResultElements { get; private set; } - - // 状态同步对象 - public bool TaskCompleted { get; private set; } - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - - // 限制返回的元素数量 - public int? Limit { get; set; } - - // 实现IWaitableExternalEventHandler接口 - public bool WaitForCompletion(int timeoutMilliseconds = 10000) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - - public void Execute(UIApplication app) - { - try - { - var uiDoc = app.ActiveUIDocument; - var doc = uiDoc.Document; - - // 获取当前选中的元素 - var selectedIds = uiDoc.Selection.GetElementIds(); - var selectedElements = selectedIds.Select(id => doc.GetElement(id)).ToList(); - - // 应用数量限制 - if (Limit.HasValue && Limit.Value > 0) - { - selectedElements = selectedElements.Take(Limit.Value).ToList(); - } - - // 转换为ElementInfo列表 - ResultElements = selectedElements.Select(element => new ElementInfo - { - Id = element.Id.GetIdValue(), - UniqueId = element.UniqueId, - Name = element.Name, - Category = element.Category?.Name - }).ToList(); - } - catch (Exception ex) - { - TaskDialog.Show("Error", "获取选中元素失败: " + ex.Message); - ResultElements = new List(); - } - finally - { - TaskCompleted = true; - _resetEvent.Set(); - } - } - - public string GetName() - { - return "获取选中元素"; - } - } -} \ No newline at end of file diff --git a/SampleCommandSet/Commands/Create/CreateWallCommand.cs b/SampleCommandSet/Commands/Create/CreateWallCommand.cs deleted file mode 100644 index d25b67a..0000000 --- a/SampleCommandSet/Commands/Create/CreateWallCommand.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Base; -using System; -using Newtonsoft.Json.Linq; - -namespace SampleCommandSet.Commands.Create -{ - /// - /// 创建墙命令 - /// - public class CreateWallCommand : ExternalEventCommandBase - { - private CreateWallEventHandler _handler => (CreateWallEventHandler)Handler; - - /// - /// 命令名称 - /// - public override string CommandName => "create_Wall"; - - /// - /// 构造函数 - /// - /// Revit UIApplication - public CreateWallCommand(UIApplication uiApp) - : base(new CreateWallEventHandler(), uiApp) - { - } - - /// - /// 执行命令 - /// - /// JSON参数 - /// 请求ID - /// 命令执行结果 - public override object Execute(JObject parameters, string requestId) - { - try - { - // 解析墙参数 - double startX = parameters["startX"].Value(); - double startY = parameters["startY"].Value(); - double endX = parameters["endX"].Value(); - double endY = parameters["endY"].Value(); - double height = parameters["height"].Value(); - double thickness = parameters["thickness"].Value(); - - // 设置墙体参数 - _handler.SetWallParameters(startX, startY, endX, endY, height, thickness); - - // 触发外部事件并等待完成 - if (RaiseAndWaitForCompletion(10000)) - { - return _handler.CreatedWallInfo; - } - else - { - throw new TimeoutException("创建墙体操作超时"); - } - } - catch (Exception ex) - { - throw new Exception($"创建墙体失败: {ex.Message}"); - } - } - } -} diff --git a/SampleCommandSet/Commands/Create/CreateWallEventHandler.cs b/SampleCommandSet/Commands/Create/CreateWallEventHandler.cs deleted file mode 100644 index e81fb20..0000000 --- a/SampleCommandSet/Commands/Create/CreateWallEventHandler.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Interfaces; -using SampleCommandSet.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace SampleCommandSet.Commands.Create -{ - /// - /// 创建墙的外部事件处理器 - /// - public class CreateWallEventHandler : IExternalEventHandler, IWaitableExternalEventHandler - { - // 创建墙的参数 - private double _startX; - private double _startY; - private double _endX; - private double _endY; - private double _height; - private double _thickness; - - // 创建的墙体信息 - private Wall _createdWall; - public WallInfo CreatedWallInfo { get; private set; } - - // 标记操作是否完成 - private bool _taskCompleted; - - // 事件等待对象 - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - - /// - /// 设置创建墙的参数 - /// - public void SetWallParameters(double startX, double startY, double endX, double endY, double height, double thickness) - { - _startX = startX; - _startY = startY; - _endX = endX; - _endY = endY; - _height = height; - _thickness = thickness; - - _taskCompleted = false; - _resetEvent.Reset(); - } - - /// - /// 等待墙创建完成 - /// - /// 超时时间(毫秒) - /// 操作是否在超时前完成 - public bool WaitForCompletion(int timeoutMilliseconds = 10000) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - - /// - /// IExternalEventHandler.Execute 实现 - /// - public void Execute(UIApplication app) - { - try - { - Document doc = app.ActiveUIDocument.Document; - - using (Transaction trans = new Transaction(doc, "创建墙体")) - { - trans.Start(); - - // 创建墙的起点和终点 - XYZ startPoint = new XYZ(_startX, _startY, 0); - XYZ endPoint = new XYZ(_endX, _endY, 0); - - // 创建墙的曲线 - Line curve = Line.CreateBound(startPoint, endPoint); - - // 获取当前文档中的墙类型 - FilteredElementCollector collector = new FilteredElementCollector(doc); - collector.OfClass(typeof(WallType)); - WallType wallType = collector.FirstOrDefault(w => w.Name.Contains("常规")) as WallType; - - // 创建墙 - _createdWall = Wall.Create( - doc, - curve, - wallType.Id, - doc.ActiveView.GenLevel.Id, - _height, - 0.0, // 墙基点偏移 - false, // 不翻转 - false); // 不是结构墙 - - trans.Commit(); - - // 获取墙的详细信息 - CreatedWallInfo = new WallInfo - { - ElementId = _createdWall.Id.IntegerValue, - StartPoint = new Models.Point { X = startPoint.X, Y = startPoint.Y, Z = 0 }, - EndPoint = new Models.Point { X = endPoint.X, Y = endPoint.Y, Z = 0 }, - Height = _height, - Thickness = _thickness, - }; - } - } - catch (Exception ex) - { - TaskDialog.Show("错误", $"创建墙体时出错: {ex.Message}"); - - } - finally - { - _taskCompleted = true; - _resetEvent.Set(); // 通知等待线程操作已完成 - } - } - - /// - /// IExternalEventHandler.GetName 实现 - /// - public string GetName() - { - return "创建墙体"; - } - } -} diff --git a/SampleCommandSet/Commands/Delete/DeleteElementCommand.cs b/SampleCommandSet/Commands/Delete/DeleteElementCommand.cs deleted file mode 100644 index 3332303..0000000 --- a/SampleCommandSet/Commands/Delete/DeleteElementCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Autodesk.Revit.UI; -using Newtonsoft.Json.Linq; -using revit_mcp_sdk.API.Base; -using revit_mcp_sdk.API.Models; -using revit_mcp_sdk.Exceptions; -using System; - -namespace SampleCommandSet.Commands.Delete -{ - public class DeleteElementCommand : ExternalEventCommandBase - { - private DeleteElementEventHandler _handler => (DeleteElementEventHandler)Handler; - public override string CommandName => "delete_element"; - public DeleteElementCommand(UIApplication uiApp) - : base(new DeleteElementEventHandler(), uiApp) - { - } - public override object Execute(JObject parameters, string requestId) - { - try - { - // 解析数组参数 - var elementIds = parameters?["elementIds"]?.ToObject(); - if (elementIds == null || elementIds.Length == 0) - { - throw new CommandExecutionException( - "元素ID列表不能为空", - JsonRPCErrorCodes.InvalidParams); - } - // 设置要删除的元素ID数组 - _handler.ElementIds = elementIds; - // 触发外部事件并等待完成 - if (RaiseAndWaitForCompletion(15000)) - { - if (_handler.IsSuccess) - { - return CommandResult.CreateSuccess(new { deleted = true, count = _handler.DeletedCount }); - } - else - { - throw new CommandExecutionException( - "删除元素失败", - JsonRPCErrorCodes.ElementDeletionFailed); - } - } - else - { - throw CreateTimeoutException(CommandName); - } - } - catch (CommandExecutionException) - { - throw; - } - catch (Exception ex) - { - throw new CommandExecutionException( - $"删除元素失败: {ex.Message}", - JsonRPCErrorCodes.InternalError); - } - } - } -} diff --git a/SampleCommandSet/Commands/Delete/DeleteElementEventHandler.cs b/SampleCommandSet/Commands/Delete/DeleteElementEventHandler.cs deleted file mode 100644 index 964959e..0000000 --- a/SampleCommandSet/Commands/Delete/DeleteElementEventHandler.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Interfaces; -using System; -using System.Collections.Generic; -using System.Threading; - -namespace SampleCommandSet.Commands.Delete -{ - public class DeleteElementEventHandler : IExternalEventHandler, IWaitableExternalEventHandler - { - // 执行结果 - public bool IsSuccess { get; private set; } - - // 成功删除的元素数量 - public int DeletedCount { get; private set; } - // 状态同步对象 - public bool TaskCompleted { get; private set; } - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - // 要删除的元素ID数组 - public string[] ElementIds { get; set; } - // 实现IWaitableExternalEventHandler接口 - public bool WaitForCompletion(int timeoutMilliseconds = 10000) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - public void Execute(UIApplication app) - { - try - { - var doc = app.ActiveUIDocument.Document; - DeletedCount = 0; - if (ElementIds == null || ElementIds.Length == 0) - { - IsSuccess = false; - return; - } - // 创建待删除元素ID集合 - List elementIdsToDelete = new List(); - List invalidIds = new List(); - foreach (var idStr in ElementIds) - { - if (int.TryParse(idStr, out int elementIdValue)) - { - var elementId = new ElementId(elementIdValue); - // 检查元素是否存在 - if (doc.GetElement(elementId) != null) - { - elementIdsToDelete.Add(elementId); - } - } - else - { - invalidIds.Add(idStr); - } - } - if (invalidIds.Count > 0) - { - TaskDialog.Show("警告", $"以下ID无效或元素不存在:{string.Join(", ", invalidIds)}"); - } - // 如果有可删除的元素,则执行删除 - if (elementIdsToDelete.Count > 0) - { - using (var transaction = new Transaction(doc, "Delete Elements")) - { - transaction.Start(); - - // 批量删除元素 - ICollection deletedIds = doc.Delete(elementIdsToDelete); - DeletedCount = deletedIds.Count; - - transaction.Commit(); - } - IsSuccess = true; - } - else - { - TaskDialog.Show("错误", "没有有效的元素可以删除"); - IsSuccess = false; - } - } - catch (Exception ex) - { - TaskDialog.Show("错误", "删除元素失败: " + ex.Message); - IsSuccess = false; - } - finally - { - TaskCompleted = true; - _resetEvent.Set(); - } - } - public string GetName() - { - return "删除元素"; - } - } -} diff --git a/SampleCommandSet/Commands/Test/SayHelloCommand.cs b/SampleCommandSet/Commands/Test/SayHelloCommand.cs deleted file mode 100644 index 490fd08..0000000 --- a/SampleCommandSet/Commands/Test/SayHelloCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Autodesk.Revit.UI; -using Newtonsoft.Json.Linq; -using revit_mcp_sdk.API.Base; -using revit_mcp_sdk.API.Models; - -namespace SampleCommandSet.Commands.Test -{ - public class SayHelloCommand : ExternalEventCommandBase - { - private SayHelloEventHandler _handler => (SayHelloEventHandler)Handler; - - public override string CommandName => "say_hello"; - - public SayHelloCommand(UIApplication uiApp) - : base(new SayHelloEventHandler(), uiApp) - { - } - - public override object Execute(JObject parameters, string requestId) - { - RaiseAndWaitForCompletion(15000); - return CommandResult.CreateSuccess(new { execute = true }); - } - } -} diff --git a/SampleCommandSet/Commands/Test/SayHelloEventHandler.cs b/SampleCommandSet/Commands/Test/SayHelloEventHandler.cs deleted file mode 100644 index f6bc3ce..0000000 --- a/SampleCommandSet/Commands/Test/SayHelloEventHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Autodesk.Revit.UI; -using revit_mcp_sdk.API.Interfaces; -using System.Threading; - -namespace SampleCommandSet.Commands.Test -{ - public class SayHelloEventHandler : IExternalEventHandler, IWaitableExternalEventHandler - { - private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); - - public bool WaitForCompletion(int timeoutMilliseconds = 10000) - { - return _resetEvent.WaitOne(timeoutMilliseconds); - } - - public void Execute(UIApplication app) - { - TaskDialog.Show("revit-mcp", "hello MCP"); - } - - public string GetName() - { - return "say hello"; - } - } -} diff --git a/SampleCommandSet/Extensions/RevitApiCompatibilityExtensions.cs b/SampleCommandSet/Extensions/RevitApiCompatibilityExtensions.cs deleted file mode 100644 index 1a21fe7..0000000 --- a/SampleCommandSet/Extensions/RevitApiCompatibilityExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Autodesk.Revit.DB; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace SampleCommandSet.Extensions -{ - /// - /// 提供 Revit API 跨版本兼容性的扩展方法 - /// - public static class RevitApiCompatibilityExtensions - { - // 缓存反射结果以提高性能 - private static readonly Lazy ElementIdValueProperty = - new Lazy(() => typeof(ElementId).GetProperty("Value")); - - private static readonly Lazy ElementIdIntegerValueProperty = - new Lazy(() => typeof(ElementId).GetProperty("IntegerValue")); - - /// - /// 以版本兼容的方式获取 ElementId 的整数值 - /// - public static int GetIdValue(this ElementId id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // 先检查是否有 Value 属性 (Revit 2022+) - if (ElementIdValueProperty.Value != null) - { - try - { - return (int)ElementIdValueProperty.Value.GetValue(id); - } - catch - { - // 失败时回退到 IntegerValue - } - } - - // 使用 IntegerValue (旧版本 Revit) - return id.IntegerValue; - } - - /// - /// 获取文档当前 Revit 版本号 - /// - public static int GetRevitVersionNumber(this Document doc) - { - if (doc == null) - throw new ArgumentNullException(nameof(doc)); - - string versionString = doc.Application.VersionNumber; - - if (int.TryParse(versionString, out int versionNumber)) - { - return versionNumber; - } - return 0; - } - - } -} diff --git a/SampleCommandSet/Models/FamilyTypeInfo.cs b/SampleCommandSet/Models/FamilyTypeInfo.cs deleted file mode 100644 index 67a2352..0000000 --- a/SampleCommandSet/Models/FamilyTypeInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace SampleCommandSet.Models -{ - public class FamilyTypeInfo - { - public long FamilyTypeId { get; set; } - public string UniqueId { get; set; } - public string FamilyName { get; set; } - public string TypeName { get; set; } - public string Category { get; set; } - } -} diff --git a/SampleCommandSet/Models/Point.cs b/SampleCommandSet/Models/Point.cs deleted file mode 100644 index a6720ec..0000000 --- a/SampleCommandSet/Models/Point.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Newtonsoft.Json; - -namespace SampleCommandSet.Models -{ - /// - /// 点坐标 - /// - public class Point - { - [JsonProperty("x")] - public double X { get; set; } - [JsonProperty("y")] - public double Y { get; set; } - [JsonProperty("z")] - public double Z { get; set; } - } -} diff --git a/SampleCommandSet/Models/ViewInfo.cs b/SampleCommandSet/Models/ViewInfo.cs deleted file mode 100644 index a177d21..0000000 --- a/SampleCommandSet/Models/ViewInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SampleCommandSet.Models -{ - /// - /// 视图信息数据结构 - /// - public class ViewInfo - { - public long Id { get; set; } - public string UniqueId { get; set; } - public string Name { get; set; } - public string ViewType { get; set; } - public bool IsTemplate { get; set; } - public int Scale { get; set; } - public string DetailLevel { get; set; } - } -} diff --git a/SampleCommandSet/Models/WallInfo.cs b/SampleCommandSet/Models/WallInfo.cs deleted file mode 100644 index 6ae3fc4..0000000 --- a/SampleCommandSet/Models/WallInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Newtonsoft.Json; - -namespace SampleCommandSet.Models -{ - /// - /// 墙体信息结构,用于返回创建的墙的详细信息 - /// - public class WallInfo - { - [JsonProperty("elementId")] - public int ElementId { get; set; } - [JsonProperty("startPoint")] - public Point StartPoint { get; set; } = new Point(); - [JsonProperty("endPoint")] - public Point EndPoint { get; set; } = new Point(); - [JsonProperty("height")] - public double Height { get; set; } - [JsonProperty("thickness")] - public double Thickness { get; set; } - } -} diff --git a/SampleCommandSet/Properties/AssemblyInfo.cs b/SampleCommandSet/Properties/AssemblyInfo.cs deleted file mode 100644 index ea88862..0000000 --- a/SampleCommandSet/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// 有关程序集的一般信息由以下 -// 控制。更改这些特性值可修改 -// 与程序集关联的信息。 -[assembly: AssemblyTitle("SampleCommandSet")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("P R C")] -[assembly: AssemblyProduct("SampleCommandSet")] -[assembly: AssemblyCopyright("Copyright © P R C 2025")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 将 ComVisible 设置为 false 会使此程序集中的类型 -//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 -//请将此类型的 ComVisible 特性设置为 true。 -[assembly: ComVisible(false)] - -// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID -[assembly: Guid("1080b354-f092-49b9-a708-53e971d6ea95")] - -// 程序集的版本信息由下列四个值组成: -// -// 主版本 -// 次版本 -// 生成号 -// 修订号 -// -//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 -//通过使用 "*",如下所示: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleCommandSet/SampleCommandSet.csproj b/SampleCommandSet/SampleCommandSet.csproj deleted file mode 100644 index d270033..0000000 --- a/SampleCommandSet/SampleCommandSet.csproj +++ /dev/null @@ -1,197 +0,0 @@ - - - - - Debug - AnyCPU - {1080B354-F092-49B9-A708-53E971D6EA95} - Library - Properties - SampleCommandSet - SampleCommandSet - v4.8 - 512 - true - - - true - ..\revit-mcp-plugin\bin\x64\Debug\commands\SampleCommandset\2019\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Release\commands\SampleCommandset\2019\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - true - ..\revit-mcp-plugin\bin\x64\Debug\commands\SampleCommandset\2020\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Release\commands\SampleCommandset\2020\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - true - ..\revit-mcp-plugin\bin\x64\Debug\commands\SampleCommandset\2021\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Release\commands\SampleCommandset\2021\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - true - ..\revit-mcp-plugin\bin\x64\Debug\commands\SampleCommandset\2022\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Release\commands\SampleCommandset\2022\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - true - ..\revit-mcp-plugin\bin\x64\Debug\commands\SampleCommandset\2023\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Release\commands\SampleCommandset\2023\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Debug\commands\SampleCommandset\2024\ - TRACE - true - pdbonly - ARM64 - 7.3 - prompt - - - ..\revit-mcp-plugin\bin\x64\Release\commands\SampleCommandset\2024\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - - - - ..\packages\Revit_API_x64.2024.0.2\lib\NET480\AdWindows.dll - False - - - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\packages\revit-mcp-sdk.1.0.0-beta.1\lib\net48\revit-mcp-sdk.dll - - - ..\packages\Revit_API_x64.2024.0.2\lib\NET480\RevitAddInUtility.dll - False - - - ..\packages\Revit_API_x64.2024.0.2\lib\NET480\RevitAPI.dll - False - - - ..\packages\Revit_API_x64.2024.0.2\lib\NET480\RevitAPIUI.dll - False - - - - - - - - - - - ..\packages\Revit_API_x64.2024.0.2\lib\NET480\UIFramework.dll - False - - - ..\packages\Revit_API_x64.2024.0.2\lib\NET480\UIFrameworkServices.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SampleCommandSet/command.json b/SampleCommandSet/command.json deleted file mode 100644 index c9ebbed..0000000 --- a/SampleCommandSet/command.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "SampleCommandSet", - "description": "Basic command collection for Revit AI assistance", - "developer": { - "name": "revit-mcp", - "email": "", - "website": "", - "organization": "revit-mcp" - }, - "commands": [ - { - "commandName": "say_hello", - "description": "Displays a greeting dialog", - "assemblyPath": "SampleCommandSet.dll" - }, - { - "commandName": "get_available_family_types", - "description": "get available family types", - "assemblyPath": "SampleCommandSet.dll" - }, - { - "commandName": "get_current_view_elements", - "description": "get current view elements", - "assemblyPath": "SampleCommandSet.dll" - }, - { - "commandName": "get_current_view_info", - "description": "get current view info", - "assemblyPath": "SampleCommandSet.dll" - }, - { - "commandName": "create_Wall", - "description": "create a wall", - "assemblyPath": "SampleCommandSet.dll" - }, - { - "commandName": "delete_element", - "description": "Deletes elements using ElementId", - "assemblyPath": "SampleCommandSet.dll" - } - ] -} \ No newline at end of file diff --git a/SampleCommandSet/packages.config b/SampleCommandSet/packages.config deleted file mode 100644 index a26c285..0000000 --- a/SampleCommandSet/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/revit-mcp-plugin/Commands/commandRegistry.json b/revit-mcp-plugin/Commands/commandRegistry.json new file mode 100644 index 0000000..6c33cc2 --- /dev/null +++ b/revit-mcp-plugin/Commands/commandRegistry.json @@ -0,0 +1,202 @@ +{ + "commands": [ + { + "commandName": "ai_element_filter", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Filters Revit elements using AI-powered criteria" + }, + { + "commandName": "color_splash", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Applies color coding to elements based on specified parameters" + }, + { + "commandName": "create_line_based_element", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Creates line-based elements like walls and beams" + }, + { + "commandName": "create_point_based_element", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Creates point-based elements like doors and furniture" + }, + { + "commandName": "create_surface_based_element", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Creates surface-based elements like floors and ceilings" + }, + { + "commandName": "operate_element", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Performs operations on selected elements (select, hide, isolate, etc.)" + }, + { + "commandName": "tag_all_walls", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Creates tags for all walls in the current view" + }, + { + "commandName": "get_available_family_types", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Retrieves all available family types in the project" + }, + { + "commandName": "get_current_view_elements", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Gets all elements visible in the current view" + }, + { + "commandName": "get_current_view_info", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Retrieves information about the current active view" + }, + { + "commandName": "get_selected_elements", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Gets information about currently selected elements" + }, + { + "commandName": "create_dimensions", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Creates dimension annotations in the model" + }, + { + "commandName": "delete_element", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Deletes elements using ElementId" + }, + { + "commandName": "send_code_to_revit", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Executes dynamic C# code within the Revit environment" + }, + { + "commandName": "get_current_document_info", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Retrieves comprehensive metadata about the currently active Revit document" + }, + { + "commandName": "get_element_parameters", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Retrieves all parameters and their current values for a specific Revit element" + }, + { + "commandName": "set_element_parameters", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Modifies parameter values for a specific Revit element" + }, + { + "commandName": "get_revit_application_info", + "assemblyPath": "RevitMCPCommandSet.dll", + "enabled": true, + "supportedRevitVersions": [], + "developer": { + "name": "Revit MCP Team", + "email": "support@revitmcp.com" + }, + "description": "Returns session-level Revit application metadata without requiring an open document" + } + ] +} diff --git a/revit-mcp-plugin/Core/Application.cs b/revit-mcp-plugin/Core/Application.cs index 4f3597b..92ef920 100644 --- a/revit-mcp-plugin/Core/Application.cs +++ b/revit-mcp-plugin/Core/Application.cs @@ -1,5 +1,7 @@ using System; using Autodesk.Revit.UI; +using Autodesk.Revit.UI.Events; +using Autodesk.Revit.ApplicationServices; using System.Reflection; using System.Windows.Media.Imaging; @@ -9,37 +11,116 @@ namespace revit_mcp_plugin.Core { public class Application : IExternalApplication { + // Keep references for adaptive UI + private static PushButton _mcpToggleButton; + private static UIApplication _uiApp; + public Result OnStartup(UIControlledApplication application) + { + // Create ribbon panel for MCP controls + RibbonPanel mcpPanel = application.CreateRibbonPanel("Revit MCP Plugin"); + + // Toggle button to start/stop MCP service + PushButtonData pushButtonData = new PushButtonData("ID_EXCMD_TOGGLE_REVIT_MCP", "Revit MCP\r\n Switch", + Assembly.GetExecutingAssembly().Location, "revit_mcp_plugin.Core.MCPServiceConnection"); + pushButtonData.ToolTip = "Start / Stop MCP server"; + // Initial images: red (stopped) before ApplicationInitialized auto-start kicks in + pushButtonData.Image = new BitmapImage(new Uri(@"U:\SW Python 3 Automations\RevitMCP\revit-mcp-plugin\revit-mcp-plugin\Core\Ressources\server_unconnected-16.png", UriKind.Absolute)); + pushButtonData.LargeImage = new BitmapImage(new Uri(@"U:\SW Python 3 Automations\RevitMCP\revit-mcp-plugin\revit-mcp-plugin\Core\Ressources\server_unconnected-32.png", UriKind.Absolute)); + pushButtonData.AvailabilityClassName = "revit_mcp_plugin.Core.MCPCommandAvailability"; + _mcpToggleButton = mcpPanel.AddItem(pushButtonData) as PushButton; + + // Settings button + PushButtonData mcp_settings_pushButtonData = new PushButtonData("ID_EXCMD_MCP_SETTINGS", "Settings", + Assembly.GetExecutingAssembly().Location, "revit_mcp_plugin.Core.Settings"); + mcp_settings_pushButtonData.ToolTip = "MCP Settings"; + mcp_settings_pushButtonData.Image = new BitmapImage(new Uri("/revit-mcp-plugin;component/Core/Ressources/settings-16.png", UriKind.RelativeOrAbsolute)); + mcp_settings_pushButtonData.LargeImage = new BitmapImage(new Uri("/revit-mcp-plugin;component/Core/Ressources/settings-32.png", UriKind.RelativeOrAbsolute)); + mcp_settings_pushButtonData.AvailabilityClassName = "revit_mcp_plugin.Core.MCPCommandAvailability"; + mcpPanel.AddItem(mcp_settings_pushButtonData); + + // Register for ApplicationInitialized event to auto-start MCP service + // This fires after Revit initialization but before any document opens + application.ControlledApplication.ApplicationInitialized += OnApplicationInitialized; + + return Result.Succeeded; + } + + private void OnApplicationInitialized(object sender, EventArgs e) { - RibbonPanel mcpPanel = application.CreateRibbonPanel("Revit MCP Plugin"); - - PushButtonData pushButtonData = new PushButtonData("ID_EXCMD_TOGGLE_REVIT_MCP", "Revit MCP\r\n Switch", - Assembly.GetExecutingAssembly().Location, "revit_mcp_plugin.Core.MCPServiceConnection"); - pushButtonData.ToolTip = "Open / Close mcp server"; - pushButtonData.Image = new BitmapImage(new Uri("/revit-mcp-plugin;component/Core/Ressources/icon-16.png", UriKind.RelativeOrAbsolute)); - pushButtonData.LargeImage = new BitmapImage(new Uri("/revit-mcp-plugin;component/Core/Ressources/icon-32.png", UriKind.RelativeOrAbsolute)); - mcpPanel.AddItem(pushButtonData); - - PushButtonData mcp_settings_pushButtonData = new PushButtonData("ID_EXCMD_MCP_SETTINGS", "Settings", - Assembly.GetExecutingAssembly().Location, "revit_mcp_plugin.Core.Settings"); - mcp_settings_pushButtonData.ToolTip = "MCP Settings"; - mcp_settings_pushButtonData.Image = new BitmapImage(new Uri("/revit-mcp-plugin;component/Core/Ressources/settings-16.png", UriKind.RelativeOrAbsolute)); - mcp_settings_pushButtonData.LargeImage = new BitmapImage(new Uri("/revit-mcp-plugin;component/Core/Ressources/settings-32.png", UriKind.RelativeOrAbsolute)); - mcpPanel.AddItem(mcp_settings_pushButtonData); + try + { + var app = (Autodesk.Revit.ApplicationServices.Application)sender; + _uiApp = new UIApplication(app); - return Result.Succeeded; + // Initialize and AUTO-START your socket service + SocketService.Instance.Initialize(_uiApp); + SocketService.Instance.RunningStateChanged += OnRunningStateChanged; + SocketService.Instance.Start(); // auto-start + + // Immediately reflect "Running" (green) in the button + UpdateToggleButtonVisual(SocketService.Instance.IsRunning); + + // Optional: a non-intrusive message (comment out if you prefer silent) + // TaskDialog.Show("MCP Plugin", "MCP service started successfully on port 8080!"); + } + catch (Exception ex) + { + TaskDialog.Show("MCP Plugin Error", + $"Failed to start MCP service: {ex.Message}\n\nCheck Revit journal for details."); + } + } + + // Event handler fired by the service whenever run/stop state changes + private void OnRunningStateChanged(object sender, bool isRunning) + { + // Ensure UI changes occur on Revit's UI thread via Idling + if (_uiApp != null) + { + EventHandler idleOnce = null; + idleOnce = (s, e) => + { + _uiApp.Idling -= idleOnce; + UpdateToggleButtonVisual(isRunning); + }; + _uiApp.Idling += idleOnce; + } + } + + // Swap images/text based on service state + private void UpdateToggleButtonVisual(bool isRunning) + { + if (_mcpToggleButton == null) return; + + string small = isRunning + ? @"U:\SW Python 3 Automations\RevitMCP\revit-mcp-plugin\revit-mcp-plugin\Core\Ressources\server_connected-16.png" + : @"U:\SW Python 3 Automations\RevitMCP\revit-mcp-plugin\revit-mcp-plugin\Core\Ressources\server_unconnected-16.png"; + + string large = isRunning + ? @"U:\SW Python 3 Automations\RevitMCP\revit-mcp-plugin\revit-mcp-plugin\Core\Ressources\server_connected-32.png" + : @"U:\SW Python 3 Automations\RevitMCP\revit-mcp-plugin\revit-mcp-plugin\Core\Ressources\server_unconnected-32.png"; + + _mcpToggleButton.Image = new BitmapImage(new Uri(small, UriKind.Absolute)); + _mcpToggleButton.LargeImage = new BitmapImage(new Uri(large, UriKind.Absolute)); + + // Optional: adjust text & tooltip + _mcpToggleButton.ItemText = isRunning ? "MCP (Running)" : "MCP (Stopped)"; + _mcpToggleButton.ToolTip = isRunning + ? "MCP server is running. Click to stop." + : "MCP server is stopped. Click to start."; } public Result OnShutdown(UIControlledApplication application) { + application.ControlledApplication.ApplicationInitialized -= OnApplicationInitialized; + try { + SocketService.Instance.RunningStateChanged -= OnRunningStateChanged; if (SocketService.Instance.IsRunning) - { SocketService.Instance.Stop(); - } } - catch { } + catch { /* swallow at shutdown to avoid blocking Revit */ } return Result.Succeeded; } diff --git a/revit-mcp-plugin/Core/MCPServiceConnection.cs b/revit-mcp-plugin/Core/MCPServiceConnection.cs index eb37d88..e2ad193 100644 --- a/revit-mcp-plugin/Core/MCPServiceConnection.cs +++ b/revit-mcp-plugin/Core/MCPServiceConnection.cs @@ -5,29 +5,35 @@ namespace revit_mcp_plugin.Core { + /// + /// Command availability class that allows MCP control even without open documents + /// + public class MCPCommandAvailability : IExternalCommandAvailability + { + public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories) + { + // Always available - doesn't require document or selection + return true; + } + } + [Transaction(TransactionMode.Manual)] public class MCPServiceConnection : IExternalCommand { + // Make command always available, even without open documents + public string AvailabilityClassName => "revit_mcp_plugin.Core.MCPCommandAvailability"; + public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { try { - // 获取socket服务 - // Obtain socket service. - SocketService service = SocketService.Instance; - - if (service.IsRunning) - { - service.Stop(); - TaskDialog.Show("revitMCP", "Close Server"); - } + // Simple toggle: start if stopped, stop if running + if (SocketService.Instance.IsRunning) + SocketService.Instance.Stop(); else - { - service.Initialize(commandData.Application); - service.Start(); - TaskDialog.Show("revitMCP", "Open Server"); - } + SocketService.Instance.Start(); + // UI updates automatically via RunningStateChanged event return Result.Succeeded; } catch (Exception ex) diff --git a/revit-mcp-plugin/Core/Ressources/Thumbs.db b/revit-mcp-plugin/Core/Ressources/Thumbs.db new file mode 100644 index 0000000..a1191fd Binary files /dev/null and b/revit-mcp-plugin/Core/Ressources/Thumbs.db differ diff --git a/revit-mcp-plugin/Core/Ressources/server_connected-16.png b/revit-mcp-plugin/Core/Ressources/server_connected-16.png new file mode 100644 index 0000000..d1bca34 Binary files /dev/null and b/revit-mcp-plugin/Core/Ressources/server_connected-16.png differ diff --git a/revit-mcp-plugin/Core/Ressources/server_connected-32.png b/revit-mcp-plugin/Core/Ressources/server_connected-32.png new file mode 100644 index 0000000..748bd10 Binary files /dev/null and b/revit-mcp-plugin/Core/Ressources/server_connected-32.png differ diff --git a/revit-mcp-plugin/Core/Ressources/server_unconnected-16.png b/revit-mcp-plugin/Core/Ressources/server_unconnected-16.png new file mode 100644 index 0000000..9da387b Binary files /dev/null and b/revit-mcp-plugin/Core/Ressources/server_unconnected-16.png differ diff --git a/revit-mcp-plugin/Core/Ressources/server_unconnected-32.png b/revit-mcp-plugin/Core/Ressources/server_unconnected-32.png new file mode 100644 index 0000000..d3dfa99 Binary files /dev/null and b/revit-mcp-plugin/Core/Ressources/server_unconnected-32.png differ diff --git a/revit-mcp-plugin/Core/SocketService.cs b/revit-mcp-plugin/Core/SocketService.cs index 9238bbe..ec0514b 100644 --- a/revit-mcp-plugin/Core/SocketService.cs +++ b/revit-mcp-plugin/Core/SocketService.cs @@ -44,77 +44,128 @@ private SocketService() public bool IsRunning => _isRunning; + public event EventHandler RunningStateChanged; + public int Port { get => _port; set => _port = value; } - // 初始化 - // Initialization. + // Initialize the socket service public void Initialize(UIApplication uiApp) { - _uiApp = uiApp; - - // 初始化事件管理器 - // Initialize ExternalEventManager - ExternalEventManager.Instance.Initialize(uiApp, _logger); - - // 记录当前 Revit 版本 - // Get the current Revit version. - var versionAdapter = new RevitMCPSDK.API.Utils.RevitVersionAdapter(_uiApp.Application); - string currentVersion = versionAdapter.GetRevitVersion(); - _logger.Info("当前 Revit 版本: {0}\nCurrent Revit version: {0}", currentVersion); + _logger.Info("SocketService: Starting initialization..."); + try + { + _uiApp = uiApp; + _logger.Info("SocketService: UIApplication assigned"); + + // Initialize ExternalEventManager + _logger.Info("SocketService: Initializing ExternalEventManager..."); + ExternalEventManager.Instance.Initialize(uiApp, _logger); + _logger.Info("SocketService: ExternalEventManager initialized"); + + // Get the current Revit version + _logger.Info("SocketService: Getting Revit version..."); + var versionAdapter = new RevitMCPSDK.API.Utils.RevitVersionAdapter(_uiApp.Application); + string currentVersion = versionAdapter.GetRevitVersion(); + _logger.Info($"Current Revit version: {currentVersion}"); + + // Create CommandExecutor + _logger.Info("SocketService: Creating CommandExecutor..."); + _commandExecutor = new CommandExecutor(_commandRegistry, _logger); + _logger.Info("SocketService: CommandExecutor created"); + + // Load configuration and register commands + _logger.Info("SocketService: Loading configuration..."); + ConfigurationManager configManager = new ConfigurationManager(_logger); + configManager.LoadConfiguration(); + _logger.Info("SocketService: Configuration loaded"); + + //// Read the service port from the configuration + //if (configManager.Config.Settings.Port > 0) + //{ + // _port = configManager.Config.Settings.Port; + //} + _port = 8080; // Hard-coded port number + _logger.Info($"SocketService: Using port {_port}"); + + // Load commands + _logger.Info("SocketService: Loading commands..."); + CommandManager commandManager = new CommandManager( + _commandRegistry, _logger, configManager, _uiApp); + commandManager.LoadCommands(); + _logger.Info("SocketService: Commands loaded"); + + _logger.Info($"Socket service initialized successfully on port {_port}"); + } + catch (Exception ex) + { + _logger.Error($"SocketService initialization failed: {ex.Message}"); + _logger.Error($"Stack trace: {ex.StackTrace}"); + throw; // Re-throw to let caller handle it + } + } + public void Start() + { + if (_isRunning) + { + _logger.Info("SocketService: Service is already running"); + return; + } - // 创建命令执行器 - // Create CommandExecutor - _commandExecutor = new CommandExecutor(_commandRegistry, _logger); + // Try to start on the configured port, with fallback ports if needed + int[] portsToTry = { _port, 8081, 8082, 8083, 8084, 8085 }; - // 加载配置并注册命令 - // Load configuration and register commands. - ConfigurationManager configManager = new ConfigurationManager(_logger); - configManager.LoadConfiguration(); - + foreach (int portToTry in portsToTry) + { + try + { + _logger.Info($"SocketService: Attempting to start TCP listener on port {portToTry}..."); - //// 从配置中读取服务端口 - //// Read the service port from the configuration. - //if (configManager.Config.Settings.Port > 0) - //{ - // _port = configManager.Config.Settings.Port; - //} - _port = 8080; // 固定端口号 - Hard-wired port number. + _isRunning = true; + _listener = new TcpListener(IPAddress.Any, portToTry); + _listener.Start(); - // 加载命令 - // Load command. - CommandManager commandManager = new CommandManager( - _commandRegistry, _logger, configManager, _uiApp); - commandManager.LoadCommands(); + _port = portToTry; // Update the actual port being used + _logger.Info($"SocketService: TCP listener started successfully on port {_port}"); - _logger.Info($"Socket service initialized on port {_port}"); - } + _listenerThread = new Thread(ListenForClients) + { + IsBackground = true, + Name = "RevitMCP-SocketListener" + }; + _listenerThread.Start(); - public void Start() - { - if (_isRunning) return; + _logger.Info("SocketService: Listener thread started"); - try - { - _isRunning = true; - _listener = new TcpListener(IPAddress.Any, _port); - _listener.Start(); + // Raise event for state change + RunningStateChanged?.Invoke(this, _isRunning); - _listenerThread = new Thread(ListenForClients) + return; // Success, exit the loop + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse) { - IsBackground = true - }; - _listenerThread.Start(); - } - catch (Exception) - { - _isRunning = false; + _logger.Warning($"SocketService: Port {portToTry} is already in use, trying next port..."); + _isRunning = false; + continue; // Try next port + } + catch (Exception ex) + { + _logger.Error($"SocketService: Failed to start socket service on port {portToTry}: {ex.Message}"); + _logger.Error($"Stack trace: {ex.StackTrace}"); + _isRunning = false; + throw; // Re-throw for critical errors (not port conflicts) + } } + + // If we get here, all ports failed + _logger.Error("SocketService: Failed to start on any available port (8080-8085)"); + _isRunning = false; + throw new Exception("Could not start socket service - all ports (8080-8085) are in use"); } public void Stop() @@ -132,6 +183,9 @@ public void Stop() { _listenerThread.Join(1000); } + + // Raise event for state change + RunningStateChanged?.Invoke(this, _isRunning); } catch (Exception) { @@ -175,8 +229,7 @@ private void HandleClientCommunication(object clientObj) while (_isRunning && tcpClient.Connected) { - // 读取客户端消息 - // Read client messages. + // Read client messages int bytesRead = 0; try @@ -185,25 +238,22 @@ private void HandleClientCommunication(object clientObj) } catch (IOException) { - // 客户端断开连接 - // Client disconnected. + // Client disconnected break; } if (bytesRead == 0) { - // 客户端断开连接 - // Client disconnected. + // Client disconnected break; } string message = Encoding.UTF8.GetString(buffer, 0, bytesRead); - System.Diagnostics.Trace.WriteLine($"收到消息: {message}\nReceived message: {message}"); + System.Diagnostics.Trace.WriteLine($"Received message: {message}"); string response = ProcessJsonRPCRequest(message); - // 发送响应 - // Send response. + // Send response byte[] responseData = Encoding.UTF8.GetBytes(response); stream.Write(responseData, 0, responseData.Length); } @@ -224,12 +274,10 @@ private string ProcessJsonRPCRequest(string requestJson) try { - // 解析JSON-RPC请求 - // Parse JSON-RPC requests. + // Parse JSON-RPC requests request = JsonConvert.DeserializeObject(requestJson); - // 验证请求格式是否有效 - // Verify that the request format is valid. + // Verify that the request format is valid if (request == null || !request.IsValid()) { return CreateErrorResponse( @@ -239,18 +287,16 @@ private string ProcessJsonRPCRequest(string requestJson) ); } - // 查找命令 - // Search for the command in the registry. + // Search for the command in the registry if (!_commandRegistry.TryGetCommand(request.Method, out var command)) { return CreateErrorResponse(request.Id, JsonRPCErrorCodes.MethodNotFound, $"Method '{request.Method}' not found"); } - // 执行命令 - // Execute command. + // Execute command try - { + { object result = command.Execute(request.GetParamsObject(), request.Id); return CreateSuccessResponse(request.Id, result); @@ -262,8 +308,7 @@ private string ProcessJsonRPCRequest(string requestJson) } catch (JsonException) { - // JSON解析错误 - // JSON parsing error. + // JSON parsing error return CreateErrorResponse( null, JsonRPCErrorCodes.ParseError, @@ -272,8 +317,7 @@ private string ProcessJsonRPCRequest(string requestJson) } catch (Exception ex) { - // 处理请求时的其他错误 - // Catch other errors produced when processing requests. + // Catch other errors produced when processing requests return CreateErrorResponse( null, JsonRPCErrorCodes.InternalError, diff --git a/revit-mcp-plugin/UI/CommandSetSettingsPage.xaml.cs b/revit-mcp-plugin/UI/CommandSetSettingsPage.xaml.cs index 10d76eb..e949abf 100644 --- a/revit-mcp-plugin/UI/CommandSetSettingsPage.xaml.cs +++ b/revit-mcp-plugin/UI/CommandSetSettingsPage.xaml.cs @@ -121,14 +121,14 @@ private void LoadCommandSets() // 如果至少有一个版本支持此命令 if (supportedCommandVersions.Count > 0 && dllBasePath != null) { - // 创建命令配置 + // 创建命令配置 - 默认启用命令 var commandConfig = new CommandConfig { CommandName = command.CommandName, Description = command.Description, // 使用带有版本占位符的路径 AssemblyPath = dllBasePath, - Enabled = false, + Enabled = true, // 默认启用所有找到的命令 // 记录所有支持的版本 SupportedRevitVersions = supportedCommandVersions.ToArray() }; @@ -406,4 +406,4 @@ public class CommandItemJson public string Description { get; set; } public string AssemblyPath { get; set; } } -} \ No newline at end of file +} diff --git a/revit-mcp-plugin/revit-mcp-plugin.csproj b/revit-mcp-plugin/revit-mcp-plugin.csproj index 4b28c0b..e7af3ef 100644 --- a/revit-mcp-plugin/revit-mcp-plugin.csproj +++ b/revit-mcp-plugin/revit-mcp-plugin.csproj @@ -76,16 +76,23 @@ + + bin\AddIn $(RevitVersion) $(Configuration)\ $(RootDir)$(RootNameSpace)\ + $(AddinDir)Commands\ + + + + @@ -93,4 +100,4 @@ - \ No newline at end of file +