├── imgs ├── demo.gif └── cursor_config.png ├── requirements.txt ├── src ├── config.json ├── __init__.py ├── nlp_processor.py ├── cad_controller.py └── server.py ├── LICENSE ├── README_zh.md ├── README_en.md └── README.md /imgs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daobataotie/CAD-MCP/HEAD/imgs/demo.gif -------------------------------------------------------------------------------- /imgs/cursor_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daobataotie/CAD-MCP/HEAD/imgs/cursor_config.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # CAD MCP 服务依赖 2 | pywin32>=228 3 | mcp>=0.1.0 4 | pydantic>=2.0.0 5 | typing>=3.7.4.3 -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "name": "CAD MCP 服务器", 4 | "version": "1.0.0" 5 | }, 6 | "cad": { 7 | "type": "AUTOCAD", 8 | "startup_wait_time": 20, 9 | "command_delay": 0.5 10 | }, 11 | "output": { 12 | "directory": "./output", 13 | "default_filename": "cad_drawing.dwg" 14 | } 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 曹瑞 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CAD MCP 服务包 3 | """ 4 | 5 | import os 6 | import json 7 | import logging 8 | 9 | # 配置日志 10 | logging.basicConfig( 11 | level=logging.INFO, 12 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' 13 | ) 14 | 15 | logger = logging.getLogger('cad_mcp') 16 | 17 | # 加载配置 18 | def load_config(): 19 | """加载配置文件""" 20 | config_path = os.path.join(os.path.dirname(__file__), 'config.json') 21 | try: 22 | with open(config_path, 'r', encoding='utf-8') as f: 23 | config = json.load(f) 24 | logger.info("配置文件加载成功") 25 | return config 26 | except Exception as e: 27 | logger.error(f"加载配置文件失败: {str(e)}") 28 | # 返回默认配置 29 | return { 30 | "server": { 31 | "name": "CAD MCP Server", 32 | "version": "1.0.0", 33 | "host": "0.0.0.0", 34 | "port": 5000, 35 | "debug": True 36 | }, 37 | "cad": { 38 | "type": "AUTOCAD", 39 | "startup_wait_time": 20, 40 | "command_delay": 0.5 41 | }, 42 | "output": { 43 | "directory": "output", 44 | "default_filename": "cad_drawing.dwg" 45 | } 46 | } 47 | 48 | # 导出配置 49 | config = load_config() 50 | 51 | __all__ = [ 52 | 'config' 53 | ] 54 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # CAD-MCP Server (CAD Model Context Protocol Server) 2 | 3 | ## 项目介绍 4 | 5 | CAD-MCP是一个创新的CAD控制服务,允许通过自然语言指令控制CAD软件进行绘图操作。该项目结合了自然语言处理和CAD自动化技术,使用户能够通过简单的文本命令创建和修改CAD图纸,无需手动操作CAD界面。 6 | 7 | ## 功能特点 8 | 9 | ### CAD控制功能 10 | 11 | - **多CAD软件支持**:支持AutoCAD、浩辰CAD(GCAD/GstarCAD)和中望CAD(ZWCAD)等主流CAD软件 12 | - **基础绘图功能**: 13 | - 直线绘制 14 | - 圆形绘制 15 | - 圆弧绘制 16 | - 矩形绘制 17 | - 多段线绘制 18 | - 文本添加 19 | - 图案填充 20 | - 尺寸标注 21 | - **图层管理**:创建和切换图层 22 | - **图纸保存**:将当前绘图保存为DWG文件 23 | 24 | ### 自然语言处理功能 25 | 26 | - **命令解析**:将自然语言指令解析为CAD操作参数 27 | - **颜色识别**:从文本中提取颜色信息并应用到绘图对象 28 | - **形状关键词映射**:支持多种形状描述词的识别 29 | - **动作关键词映射**:识别各种绘图和编辑动作 30 | 31 | ## Demo 32 | 33 | The following is the demo video. 34 | 35 | ![Demo](imgs/demo.gif) 36 | 37 | ## 安装要求 38 | 39 | ### 依赖项 40 | 41 | ``` 42 | pywin32>=228 # Windows COM接口支持 43 | mcp>=0.1.0 # Model Control Protocol库 44 | pydantic>=2.0.0 # 数据验证 45 | typing>=3.7.4.3 # 类型注解支持 46 | ``` 47 | 48 | ### 系统要求 49 | 50 | - Windows操作系统 51 | - 已安装的CAD软件(AutoCAD、浩辰CAD或中望CAD) 52 | 53 | ## 配置说明 54 | 55 | 配置文件位于`src/config.json`,包含以下主要设置: 56 | 57 | ```json 58 | { 59 | "server": { 60 | "name": "CAD MCP 服务器", 61 | "version": "1.0.0" 62 | }, 63 | "cad": { 64 | "type": "AutoCAD", 65 | "startup_wait_time": 20, 66 | "command_delay": 0.5 67 | }, 68 | "output": { 69 | "directory": "./output", 70 | "default_filename": "cad_drawing.dwg" 71 | } 72 | } 73 | ``` 74 | 75 | - **server**: 服务器名称和版本信息 76 | - **cad**: 77 | - `type`: CAD软件类型(AutoCAD、GCAD、GstarCAD或ZWCAD) 78 | - `startup_wait_time`: CAD启动等待时间(秒) 79 | - `command_delay`: 命令执行延迟(秒) 80 | - **output**: 输出文件设置 81 | 82 | ## 使用方法 83 | 84 | ### 启动服务 85 | 86 | ``` 87 | python src/server.py 88 | ``` 89 | 90 | ### Claude Desktop 、 Windsurf 91 | 92 | ```bash 93 | # add to claude_desktop_config.json. Note:use your path 94 | { 95 | "mcpServers": { 96 | "CAD": { 97 | "command": "python", 98 | "args": [ 99 | # your path,e.g.:"C:\\cad-mcp\\src\\server.py" 100 | "~/server.py" 101 | ] 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ### Cursor 108 | 109 | ```bash 110 | # Add according to the following diagram Cursor MCP. Note:use your path 111 | ``` 112 | ![Cursor config](imgs/cursor_config.png) 113 | 114 | 说明:新版Cursor也改为了json配置,参见上一节 115 | 116 | ### MCP Inspector 117 | 118 | ```bash 119 | # Note:use your path 120 | npx -y @modelcontextprotocol/inspector python C:\\cad-mcp\\src\\server.py 121 | ``` 122 | 123 | ### 服务API 124 | 125 | 服务器提供以下主要API功能: 126 | 127 | - `draw_line`: 绘制直线 128 | - `draw_circle`: 绘制圆 129 | - `draw_arc`: 绘制弧 130 | - `draw_polyline`: 绘制多段线 131 | - `draw_rectangle`: 绘制矩形 132 | - `draw_text`: 添加文本 133 | - `draw_hatch`: 绘制填充 134 | - `add_dimension`: 添加线性标注 135 | - `save_drawing`: 保存图纸 136 | - `process_command`: 处理自然语言命令 137 | 138 | ## 项目结构 139 | 140 | ``` 141 | CAD-MCP/ 142 | ├── imgs/ # 图像和视频资源 143 | │ └── CAD-mcp.mp4 # 演示视频 144 | ├── requirements.txt # 项目依赖 145 | └── src/ # 源代码 146 | ├── __init__.py # 包初始化 147 | ├── cad_controller.py # CAD控制器 148 | ├── config.json # 配置文件 149 | ├── nlp_processor.py # 自然语言处理器 150 | └── server.py # 服务器实现 151 | ``` 152 | 153 | ## 许可证 154 | 155 | MIT License 156 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # CAD-MCP Server (CAD Model Context Protocol Server) 2 | 3 | ## Project Introduction 4 | 5 | CAD-MCP is an innovative CAD control service that allows controlling CAD software for drawing operations through natural language instructions. This project combines natural language processing and CAD automation technology, enabling users to create and modify CAD drawings through simple text commands without manually operating the CAD interface. 6 | 7 | ## Features 8 | 9 | ### CAD Control Functions 10 | 11 | - **Multiple CAD Software Support**: Supports mainstream CAD software including AutoCAD, GstarCAD (GCAD) and ZWCAD 12 | - **Basic Drawing Functions**: 13 | - Line drawing 14 | - Circle drawing 15 | - Arc drawing 16 | - Rectangle drawing 17 | - Polyline drawing 18 | - Text addition 19 | - Pattern filling 20 | - Dimension annotation 21 | - **Layer Management**: Create and switch layers 22 | - **Drawing Save**: Save the current drawing as a DWG file 23 | 24 | ### Natural Language Processing Functions 25 | 26 | - **Command Parsing**: Parse natural language instructions into CAD operation parameters 27 | - **Color Recognition**: Extract color information from text and apply it to drawing objects 28 | - **Shape Keyword Mapping**: Support recognition of various shape description words 29 | - **Action Keyword Mapping**: Recognize various drawing and editing actions 30 | 31 | ## Demo 32 | 33 | The following is the demo video. 34 | 35 | ![Demo](imgs/demo.gif) 36 | 37 | ## Installation Requirements 38 | 39 | ### Dependencies 40 | 41 | ``` 42 | pywin32>=228 # Windows COM interface support 43 | mcp>=0.1.0 # Model Control Protocol library 44 | pydantic>=2.0.0 # Data validation 45 | typing>=3.7.4.3 # Type annotation support 46 | ``` 47 | 48 | ### System Requirements 49 | 50 | - Windows operating system 51 | - Installed CAD software (AutoCAD, GstarCAD, or ZWCAD) 52 | 53 | ## Configuration 54 | 55 | The configuration file is located at `src/config.json` and contains the following main settings: 56 | 57 | ```json 58 | { 59 | "server": { 60 | "name": "CAD MCP Server", 61 | "version": "1.0.0" 62 | }, 63 | "cad": { 64 | "type": "AutoCAD", 65 | "startup_wait_time": 20, 66 | "command_delay": 0.5 67 | }, 68 | "output": { 69 | "directory": "./output", 70 | "default_filename": "cad_drawing.dwg" 71 | } 72 | } 73 | ``` 74 | 75 | - **server**: Server name and version information 76 | - **cad**: 77 | - `type`: CAD software type (AutoCAD, GCAD, GstarCAD, or ZWCAD) 78 | - `startup_wait_time`: CAD startup waiting time (seconds) 79 | - `command_delay`: Command execution delay (seconds) 80 | - **output**: Output file settings 81 | 82 | ## Usage 83 | 84 | ### Starting the Service 85 | 86 | ``` 87 | python src/server.py 88 | ``` 89 | 90 | ### Claude Desktop & Windsurf 91 | 92 | ```bash 93 | # add to claude_desktop_config.json. Note: use your path 94 | { 95 | "mcpServers": { 96 | "CAD": { 97 | "command": "python", 98 | "args": [ 99 | # your path, e.g.: "C:\\cad-mcp\\src\\server.py" 100 | "~/server.py" 101 | ] 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ### Cursor 108 | 109 | ```bash 110 | # Add according to the following diagram Cursor MCP. Note: use your path 111 | ``` 112 | ![Cursor config](imgs/cursor_config.png) 113 | 114 | Note:The new version of cursor has also been changed to JSON configuration, please refer to the previous section 115 | 116 | ### MCP Inspector 117 | 118 | ```bash 119 | # Note: use your path 120 | npx -y @modelcontextprotocol/inspector python C:\\cad-mcp\\src\\server.py 121 | ``` 122 | 123 | ### Service API 124 | 125 | The server provides the following main API functions: 126 | 127 | - `draw_line`: Draw a line 128 | - `draw_circle`: Draw a circle 129 | - `draw_arc`: Draw an arc 130 | - `draw_polyline`: Draw a polyline 131 | - `draw_rectangle`: Draw a rectangle 132 | - `draw_text`: Add text 133 | - `draw_hatch`: Draw a hatch pattern 134 | - `add_dimension`: Add linear dimension 135 | - `save_drawing`: Save the drawing 136 | - `process_command`: Process natural language commands 137 | 138 | ## Project Structure 139 | 140 | ``` 141 | CAD-MCP/ 142 | ├── imgs/ # Images and video resources 143 | │ └── CAD-mcp.mp4 # Demo video 144 | ├── requirements.txt # Project dependencies 145 | └── src/ # Source code 146 | ├── __init__.py # Package initialization 147 | ├── cad_controller.py # CAD controller 148 | ├── config.json # Configuration file 149 | ├── nlp_processor.py # Natural language processor 150 | └── server.py # Server implementation 151 | ``` 152 | 153 | ## License 154 | 155 | MIT License 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CAD-MCP Server (CAD Model Context Protocol Server) 2 | 3 | [English](/README_en.md) | [中文](/README_zh.md) 4 | 5 | ## Project Introduction 6 | 7 | CAD-MCP is an innovative CAD control service that allows controlling CAD software for drawing operations through natural language instructions. This project combines natural language processing and CAD automation technology, enabling users to create and modify CAD drawings through simple text commands without manually operating the CAD interface. 8 | 9 | ## Features 10 | 11 | ### CAD Control Functions 12 | 13 | - **Multiple CAD Software Support**: Supports mainstream CAD software including AutoCAD, GstarCAD (GCAD) and ZWCAD 14 | - **Basic Drawing Functions**: 15 | - Line drawing 16 | - Circle drawing 17 | - Arc drawing 18 | - Rectangle drawing 19 | - Polyline drawing 20 | - Text addition 21 | - Pattern filling 22 | - Dimension annotation 23 | - **Layer Management**: Create and switch layers 24 | - **Drawing Save**: Save the current drawing as a DWG file 25 | 26 | ### Natural Language Processing Functions 27 | 28 | - **Command Parsing**: Parse natural language instructions into CAD operation parameters 29 | - **Color Recognition**: Extract color information from text and apply it to drawing objects 30 | - **Shape Keyword Mapping**: Support recognition of various shape description words 31 | - **Action Keyword Mapping**: Recognize various drawing and editing actions 32 | 33 | ## Demo 34 | 35 | The following is the demo video. 36 | 37 | ![Demo](imgs/demo.gif) 38 | 39 | ## Installation Requirements 40 | 41 | ### Dependencies 42 | 43 | ``` 44 | pywin32>=228 # Windows COM interface support 45 | mcp>=0.1.0 # Model Control Protocol library 46 | pydantic>=2.0.0 # Data validation 47 | typing>=3.7.4.3 # Type annotation support 48 | ``` 49 | 50 | ### System Requirements 51 | 52 | - Windows operating system 53 | - Installed CAD software (AutoCAD, GstarCAD, or ZWCAD) 54 | 55 | ## Configuration 56 | 57 | The configuration file is located at `src/config.json` and contains the following main settings: 58 | 59 | ```json 60 | { 61 | "server": { 62 | "name": "CAD MCP Server", 63 | "version": "1.0.0" 64 | }, 65 | "cad": { 66 | "type": "AutoCAD", 67 | "startup_wait_time": 20, 68 | "command_delay": 0.5 69 | }, 70 | "output": { 71 | "directory": "./output", 72 | "default_filename": "cad_drawing.dwg" 73 | } 74 | } 75 | ``` 76 | 77 | - **server**: Server name and version information 78 | - **cad**: 79 | - `type`: CAD software type (AutoCAD, GCAD, GstarCAD, or ZWCAD) 80 | - `startup_wait_time`: CAD startup waiting time (seconds) 81 | - `command_delay`: Command execution delay (seconds) 82 | - **output**: Output file settings 83 | 84 | ## Usage 85 | 86 | ### Starting the Service 87 | 88 | ``` 89 | python src/server.py 90 | ``` 91 | 92 | ### Claude Desktop & Windsurf 93 | 94 | ```bash 95 | # add to claude_desktop_config.json. Note: use your path 96 | { 97 | "mcpServers": { 98 | "CAD": { 99 | "command": "python", 100 | "args": [ 101 | # your path, e.g.: "C:\\cad-mcp\\src\\server.py" 102 | "~/server.py" 103 | ] 104 | } 105 | } 106 | } 107 | ``` 108 | 109 | ### Cursor 110 | 111 | ```bash 112 | # Add according to the following diagram Cursor MCP. Note: use your path 113 | ``` 114 | ![Cursor config](imgs/cursor_config.png) 115 | 116 | Note:The new version of cursor has also been changed to JSON configuration, please refer to the previous section 117 | 118 | ### MCP Inspector 119 | 120 | ```bash 121 | # Note: use your path 122 | npx -y @modelcontextprotocol/inspector python C:\\cad-mcp\\src\\server.py 123 | ``` 124 | 125 | ### Service API 126 | 127 | The server provides the following main API functions: 128 | 129 | - `draw_line`: Draw a line 130 | - `draw_circle`: Draw a circle 131 | - `draw_arc`: Draw an arc 132 | - `draw_polyline`: Draw a polyline 133 | - `draw_rectangle`: Draw a rectangle 134 | - `draw_text`: Add text 135 | - `draw_hatch`: Draw a hatch pattern 136 | - `add_dimension`: Add linear dimension 137 | - `save_drawing`: Save the drawing 138 | - `process_command`: Process natural language commands 139 | 140 | ## Project Structure 141 | 142 | ``` 143 | CAD-MCP/ 144 | ├── imgs/ # Images and video resources 145 | │ └── CAD-mcp.mp4 # Demo video 146 | ├── requirements.txt # Project dependencies 147 | └── src/ # Source code 148 | ├── __init__.py # Package initialization 149 | ├── cad_controller.py # CAD controller 150 | ├── config.json # Configuration file 151 | ├── nlp_processor.py # Natural language processor 152 | └── server.py # Server implementation 153 | ``` 154 | 155 | ## License 156 | 157 | MIT License 158 | -------------------------------------------------------------------------------- /src/nlp_processor.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | import math 4 | from typing import Any, Dict, List, Optional, Tuple 5 | import json 6 | import os 7 | import os.path 8 | 9 | # 直接读取config.json文件 10 | config_path = os.path.join(os.path.dirname(__file__), 'config.json') 11 | with open(config_path, 'r', encoding='utf-8') as f: 12 | config = json.load(f) 13 | 14 | logger = logging.getLogger('nlp_processor') 15 | 16 | class NLPProcessor: 17 | """自然语言处理器类,负责解析用户指令并转换为CAD操作""" 18 | 19 | def __init__(self): 20 | """ 21 | 初始化NLP处理器 22 | """ 23 | 24 | self.logger = logging.getLogger(__name__) 25 | self.logger.info("自然语言处理器已初始化") 26 | 27 | # 对于老版本的CAD不支持设置RGB颜色,所以为了兼容性,按照索引设置颜色 28 | 29 | # 详细颜色名称到RGB值的映射 30 | self.color_rgb_map = { 31 | # 基本颜色 32 | "红色": 1, 33 | "黄色": 2, 34 | "绿色": 3, 35 | "青色": 4, 36 | "蓝色": 5, 37 | "洋红色": 6, 38 | "白色": 7, 39 | "灰色": 8, 40 | "浅灰色": 9, 41 | "黑色": 250, 42 | "棕色": 251, 43 | "橙色": 30, 44 | "紫色": 200, 45 | "粉色": 221, 46 | 47 | 48 | # 基本颜色 - 英文键 49 | "Red": 1, 50 | "Yellow": 2, 51 | "Green": 3, 52 | "Cyan": 4, 53 | "Blue": 5, 54 | "Magenta": 6, 55 | "White": 7, 56 | "Gray": 8, 57 | "Light Gray": 9, 58 | "Black": 250, 59 | "Brown": 251, 60 | "Orange": 30, 61 | "Purple": 200, 62 | "Pink": 221, 63 | 64 | } 65 | 66 | # 扩展关键词映射 67 | self.shape_keywords = { 68 | # 基本形状 69 | "直线": "line", "线": "line", 70 | "圆": "circle", "圆形": "circle", 71 | "弧": "arc", "圆弧": "arc", 72 | "矩形": "rectangle", "方形": "rectangle", "正方形": "square", 73 | "多段线": "polyline", "折线": "polyline", 74 | "文本": "text", "文字": "text", 75 | "标注": "dimension", "尺寸标注": "dimension", 76 | 77 | } 78 | 79 | # 动作关键词映射 80 | self.action_keywords = { 81 | # 基本动作 82 | "画": "draw", "绘制": "draw", "创建": "draw", "添加": "draw", 83 | "修改": "modify", "调整": "modify", "改变": "modify", 84 | "移动": "move", "旋转": "rotate", "缩放": "scale", 85 | "放大": "scale_up", "缩小": "scale_down", 86 | "删除": "erase", "擦除": "erase", "移除": "erase", 87 | "保存": "save", 88 | 89 | # 专业操作 90 | "标注": "dimension", 91 | "填充": "hatch", 92 | "创建图层": "create_layer", 93 | "切换图层": "change_layer" 94 | } 95 | 96 | 97 | def extract_color_from_command(self, command: str) -> Optional[int]: 98 | 99 | if command is None: 100 | return 7 101 | 102 | try: 103 | num = int(command) 104 | if num >= 1 and num <= 255: 105 | return num 106 | except: 107 | pass 108 | 109 | # 将命令转换为小写 110 | command = command.lower() 111 | 112 | # 尝试匹配颜色名称 113 | for color_name in self.color_rgb_map.keys(): 114 | if color_name.lower() in command: 115 | return self.color_rgb_map[color_name] 116 | 117 | # 尝试匹配颜色描述(如"淡蓝色") 118 | color_pattern = r'([深浅淡]?[a-zA-Z\u4e00-\u9fa5]+色)' 119 | color_matches = re.findall(color_pattern, command) 120 | 121 | for color_match in color_matches: 122 | # 检查是否是已知的颜色名称 123 | if color_match in self.color_rgb_map: 124 | return self.color_rgb_map[color_match] 125 | 126 | # 如果找不到颜色信息,返回7 默认白色 127 | return 7 128 | 129 | def process_command(self, command: str) -> Dict[str, Any]: 130 | """处理自然语言命令并返回结果""" 131 | self.logger.info(f"处理命令: {command}") 132 | 133 | # 解析命令 134 | parsed_command = self.parse_command(command) 135 | 136 | # 返回解析结果 137 | return parsed_command 138 | 139 | def parse_command(self, command: str) -> Dict[str, Any]: 140 | """解析自然语言命令并转换为CAD操作参数""" 141 | self.logger.info(f"解析命令: {command}") 142 | 143 | # 将命令转换为小写并去除多余空格 144 | command = command.lower().strip() 145 | 146 | # 尝试识别命令类型 147 | command_type = self._identify_command_type(command) 148 | self.logger.debug(f"识别到的命令类型: {command_type}") 149 | 150 | # 根据命令类型分发到不同的处理函数 151 | if command_type == "draw_line": 152 | return self._parse_draw_line(command) 153 | elif command_type == "draw_circle": 154 | return self._parse_draw_circle(command) 155 | elif command_type == "draw_arc": 156 | return self._parse_draw_arc(command) 157 | elif command_type == "draw_rectangle": 158 | return self._parse_draw_rectangle(command) 159 | elif command_type == "draw_polyline": 160 | return self._parse_draw_polyline(command) 161 | elif command_type == "draw_text": 162 | return self._parse_draw_text(command) 163 | elif command_type == "draw_hatch": 164 | return self._parse_draw_hatch(command) 165 | elif command_type == "save": 166 | return self._parse_save(command) 167 | else: 168 | # 默认返回一个错误结果 169 | return { 170 | "type": "unknown", 171 | "error": "无法识别的命令类型", 172 | "original_command": command 173 | } 174 | 175 | def _identify_command_type(self, command: str) -> str: 176 | """识别命令类型""" 177 | # 检查操作类型 178 | for action, action_type in self.action_keywords.items(): 179 | if action in command: 180 | # 基本形状处理 181 | for shape, shape_type in self.shape_keywords.items(): 182 | if shape in command: 183 | if action_type == "draw": 184 | if shape_type == "line": 185 | return "draw_line" 186 | elif shape_type == "circle": 187 | return "draw_circle" 188 | elif shape_type == "arc": 189 | return "draw_arc" 190 | elif shape_type in ["rectangle", "square"]: 191 | return "draw_rectangle" 192 | elif shape_type == "polyline": 193 | return "draw_polyline" 194 | elif shape_type == "text": 195 | return "draw_text" 196 | elif shape_type == "dimension": 197 | return "add_dimension" 198 | 199 | # 检查是否是创建图层命令 200 | if "图层" in command and any(action in command for action in ["创建", "新建", "添加"]): 201 | return "create_layer" 202 | 203 | # 检查是否是标注命令 204 | if "标注" in command: 205 | return "add_dimension" 206 | 207 | if "保存" in command: 208 | return "save" 209 | 210 | # 如果无法识别,返回未知类型 211 | return "unknown" 212 | 213 | def _extract_coordinates(self, text: str) -> List[Tuple[float, float, float]]: 214 | """从文本中提取坐标点""" 215 | # 匹配坐标格式: (x,y,z) 或 (x,y) 或 x,y,z 或 x,y 216 | pattern = r'\(?\s*(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)(?:\s*,\s*(-?\d+\.?\d*))?\s*\)?' 217 | matches = re.finditer(pattern, text) 218 | 219 | coordinates = [] 220 | for match in matches: 221 | x = float(match.group(1)) 222 | y = float(match.group(2)) 223 | z = float(match.group(3)) if match.group(3) else 0.0 224 | coordinates.append((x, y, z)) 225 | 226 | return coordinates 227 | 228 | def _extract_numbers(self, text: str) -> List[float]: 229 | """从文本中提取数字""" 230 | pattern = r'(-?\d+\.?\d*)' 231 | matches = re.findall(pattern, text) 232 | return [float(match) for match in matches] 233 | 234 | def _parse_draw_line(self, command: str) -> Dict[str, Any]: 235 | """解析绘制直线命令""" 236 | # 尝试提取坐标 237 | coordinates = self._extract_coordinates(command) 238 | 239 | if len(coordinates) >= 2: 240 | # 如果找到至少两个坐标点,使用前两个作为起点和终点 241 | return { 242 | "type": "draw_line", 243 | "start_point": coordinates[0], 244 | "end_point": coordinates[1] 245 | } 246 | else: 247 | # 如果没有找到足够的坐标点,尝试使用默认值 248 | # 这里可以根据需要设置默认的起点和终点 249 | return { 250 | "type": "draw_line", 251 | "start_point": (0, 0, 0), 252 | "end_point": (100, 100, 0), 253 | "note": "使用默认坐标,因为命令中未提供足够的坐标信息" 254 | } 255 | 256 | def _parse_draw_circle(self, command: str) -> Dict[str, Any]: 257 | """解析绘制圆命令""" 258 | # 尝试提取坐标和半径 259 | coordinates = self._extract_coordinates(command) 260 | numbers = self._extract_numbers(command) 261 | 262 | # 提取半径 263 | radius = None 264 | radius_pattern = r'(?:半径|r|radius)[^\d]*?(-?\d+\.?\d*)' 265 | radius_match = re.search(radius_pattern, command, re.IGNORECASE) 266 | if radius_match: 267 | radius = float(radius_match.group(1)) 268 | elif len(numbers) > 0: 269 | # 如果没有明确指定半径,使用找到的第一个数字作为半径 270 | radius = numbers[0] 271 | else: 272 | # 默认半径 273 | radius = 50.0 274 | 275 | # 提取中心点 276 | center = None 277 | if len(coordinates) > 0: 278 | center = coordinates[0] 279 | else: 280 | # 默认中心点 281 | center = (0, 0, 0) 282 | 283 | return { 284 | "type": "draw_circle", 285 | "center": center, 286 | "radius": radius 287 | } 288 | 289 | def _parse_draw_arc(self, command: str) -> Dict[str, Any]: 290 | """解析绘制圆弧命令""" 291 | # 尝试提取坐标、半径和角度 292 | coordinates = self._extract_coordinates(command) 293 | numbers = self._extract_numbers(command) 294 | 295 | # 提取中心点 296 | center = None 297 | if len(coordinates) > 0: 298 | center = coordinates[0] 299 | else: 300 | # 默认中心点 301 | center = (0, 0, 0) 302 | 303 | # 提取半径 304 | radius = None 305 | radius_pattern = r'(?:半径|r|radius)[^\d]*?(-?\d+\.?\d*)' 306 | radius_match = re.search(radius_pattern, command, re.IGNORECASE) 307 | if radius_match: 308 | radius = float(radius_match.group(1)) 309 | elif len(numbers) > 0: 310 | # 如果没有明确指定半径,使用找到的第一个数字作为半径 311 | radius = numbers[0] 312 | else: 313 | # 默认半径 314 | radius = 50.0 315 | 316 | # 提取起始角度和结束角度 317 | start_angle = 0.0 318 | end_angle = 90.0 319 | 320 | start_angle_pattern = r'(?:起始角度|start angle)[^\d]*?(-?\d+\.?\d*)' 321 | start_angle_match = re.search(start_angle_pattern, command, re.IGNORECASE) 322 | if start_angle_match: 323 | start_angle = float(start_angle_match.group(1)) 324 | 325 | end_angle_pattern = r'(?:结束角度|end angle)[^\d]*?(-?\d+\.?\d*)' 326 | end_angle_match = re.search(end_angle_pattern, command, re.IGNORECASE) 327 | if end_angle_match: 328 | end_angle = float(end_angle_match.group(1)) 329 | 330 | # 确保所有必要参数都有值 331 | if center is None: 332 | center = (0, 0, 0) 333 | if radius is None: 334 | radius = 50.0 335 | if start_angle is None: 336 | start_angle = 0.0 337 | if end_angle is None: 338 | end_angle = 90.0 339 | 340 | self.logger.debug(f"解析圆弧命令结果: 中心点={center}, 半径={radius}, 起始角度={start_angle}, 结束角度={end_angle}") 341 | 342 | return { 343 | "type": "draw_arc", 344 | "center": center, 345 | "radius": radius, 346 | "start_angle": start_angle, 347 | "end_angle": end_angle 348 | } 349 | 350 | def _parse_draw_rectangle(self, command: str) -> Dict[str, Any]: 351 | """解析绘制矩形命令""" 352 | # 尝试提取坐标 353 | coordinates = self._extract_coordinates(command) 354 | 355 | if len(coordinates) >= 2: 356 | # 如果找到至少两个坐标点,使用前两个作为对角点 357 | return { 358 | "type": "draw_rectangle", 359 | "corner1": coordinates[0], 360 | "corner2": coordinates[1] 361 | } 362 | else: 363 | # 如果没有找到足够的坐标点,尝试提取宽度和高度 364 | width = 100.0 365 | height = 100.0 366 | 367 | width_pattern = r'(?:宽度|width)[^\d]*?(-?\d+\.?\d*)' 368 | width_match = re.search(width_pattern, command, re.IGNORECASE) 369 | if width_match: 370 | width = float(width_match.group(1)) 371 | 372 | height_pattern = r'(?:高度|height)[^\d]*?(-?\d+\.?\d*)' 373 | height_match = re.search(height_pattern, command, re.IGNORECASE) 374 | if height_match: 375 | height = float(height_match.group(1)) 376 | 377 | # 如果找到一个坐标点,使用它作为起点 378 | if len(coordinates) == 1: 379 | corner1 = coordinates[0] 380 | corner2 = (corner1[0] + width, corner1[1] + height, corner1[2]) 381 | else: 382 | # 默认起点和终点 383 | corner1 = (0, 0, 0) 384 | corner2 = (width, height, 0) 385 | 386 | return { 387 | "type": "draw_rectangle", 388 | "corner1": corner1, 389 | "corner2": corner2 390 | } 391 | 392 | def _parse_draw_polyline(self, command: str) -> Dict[str, Any]: 393 | """解析绘制多段线命令""" 394 | # 尝试提取坐标 395 | coordinates = self._extract_coordinates(command) 396 | 397 | # 检查是否需要闭合 398 | closed = "闭合" in command or "封闭" in command 399 | 400 | if len(coordinates) >= 2: 401 | # 如果找到至少两个坐标点,使用它们作为多段线的点 402 | return { 403 | "type": "draw_polyline", 404 | "points": coordinates, 405 | "closed": closed 406 | } 407 | else: 408 | # 如果没有找到足够的坐标点,返回错误 409 | return { 410 | "type": "error", 411 | "message": "绘制多段线需要至少两个坐标点" 412 | } 413 | 414 | def _parse_draw_text(self, command: str) -> Dict[str, Any]: 415 | """解析绘制文本命令""" 416 | # 尝试提取坐标 417 | coordinates = self._extract_coordinates(command) 418 | 419 | # 提取文本内容 420 | text_pattern = r'[文本内容|text|内容][::]\s*[\"\'](.*?)[\"\']' 421 | text_match = re.search(text_pattern, command) 422 | 423 | text = "" 424 | if text_match: 425 | text = text_match.group(1) 426 | else: 427 | # 尝试提取引号中的内容作为文本 428 | quote_pattern = r'[\"\'](.*?)[\"\']' 429 | quote_match = re.search(quote_pattern, command) 430 | if quote_match: 431 | text = quote_match.group(1) 432 | else: 433 | # 如果没有找到引号中的内容,使用默认文本 434 | text = "示例文本" 435 | 436 | # 提取文本高度 437 | height = 2.5 # 默认高度 438 | height_pattern = r'(?:高度|height)[^\d]*?(-?\d+\.?\d*)' 439 | height_match = re.search(height_pattern, command, re.IGNORECASE) 440 | if height_match: 441 | height = float(height_match.group(1)) 442 | 443 | # 提取旋转角度 444 | rotation = 0.0 # 默认角度 445 | rotation_pattern = r'(?:旋转|角度|rotation)[^\d]*?(-?\d+\.?\d*)' 446 | rotation_match = re.search(rotation_pattern, command, re.IGNORECASE) 447 | if rotation_match: 448 | rotation = float(rotation_match.group(1)) 449 | 450 | # 提取插入点 451 | position = None 452 | if len(coordinates) > 0: 453 | position = coordinates[0] 454 | else: 455 | # 默认插入点 456 | position = (0, 0, 0) 457 | 458 | return { 459 | "type": "draw_text", 460 | "position": position, 461 | "text": text, 462 | "height": height, 463 | "rotation": rotation 464 | } 465 | 466 | def _parse_draw_hatch(self, command: str) -> Dict[str, Any]: 467 | """解析绘制填充命令""" 468 | # 尝试提取坐标点集 469 | coordinates = self._extract_coordinates(command) 470 | 471 | # 提取填充图案名称 472 | pattern_name = "SOLID" # 默认为实体填充 473 | pattern_patterns = [ 474 | r'(?:图案|pattern)[^\w]*?["\'](.*?)["\']\'', 475 | r'(?:图案|pattern)[^\w]*?(\w+)' 476 | ] 477 | 478 | for pattern in pattern_patterns: 479 | pattern_match = re.search(pattern, command, re.IGNORECASE) 480 | if pattern_match: 481 | pattern_name = pattern_match.group(1).upper() 482 | break 483 | 484 | # 提取填充比例 485 | scale = 1.0 # 默认比例 486 | scale_pattern = r'(?:比例|缩放|scale)[^\d]*?(\d+\.?\d*)' 487 | scale_match = re.search(scale_pattern, command, re.IGNORECASE) 488 | if scale_match: 489 | scale = float(scale_match.group(1)) 490 | 491 | # 检查是否有足够的点来定义填充边界 492 | if len(coordinates) >= 3: 493 | self.logger.debug(f"解析填充命令结果: 点集={coordinates}, 图案={pattern_name}, 比例={scale}") 494 | return { 495 | "type": "draw_hatch", 496 | "points": coordinates, 497 | "pattern_name": pattern_name, 498 | "scale": scale 499 | } 500 | else: 501 | # 如果没有足够的点,返回错误信息 502 | self.logger.warning("填充命令解析失败: 需要至少3个点来定义填充边界") 503 | return { 504 | "type": "error", 505 | "message": "绘制填充需要至少3个点来定义边界" 506 | } 507 | 508 | 509 | def _parse_save(self, command: str) -> Dict[str, Any]: 510 | """解析保存命令""" 511 | # 尝试提取文件路径 512 | path_pattern = r'(?:路径|保存到|path)[^\w]*?[\"\'](.*?)[\"\']' 513 | path_match = re.search(path_pattern, command, re.IGNORECASE) 514 | 515 | if path_match: 516 | file_path = path_match.group(1) 517 | else: 518 | # 默认文件路径 519 | file_path = os.path.join(config["output"]["directory"], config["output"]["default_filename"]) 520 | 521 | return { 522 | "type": "save", 523 | "file_path": file_path 524 | } -------------------------------------------------------------------------------- /src/cad_controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import math 3 | import time 4 | import os 5 | import json 6 | from typing import Any, Dict, List, Optional, Tuple, Union 7 | 8 | # 直接读取config.json文件 9 | config_path = os.path.join(os.path.dirname(__file__), 'config.json') 10 | with open(config_path, 'r', encoding='utf-8') as f: 11 | config = json.load(f) 12 | 13 | try: 14 | import win32com.client 15 | # pythoncom是pywin32的一部分,不需要单独安装 16 | import pythoncom 17 | except ImportError: 18 | logging.error("无法导入win32com.client或pythoncom,请确保已安装pywin32库") 19 | raise 20 | 21 | logger = logging.getLogger('cad_controller') 22 | 23 | class CADController: 24 | """CAD控制器类,负责与CAD应用程序交互""" 25 | 26 | def __init__(self): 27 | """初始化CAD控制器""" 28 | self.app = None 29 | self.doc = None 30 | self.entities = {} # 存储已创建图形的实体引用,用于后续修改 31 | # 从配置文件加载参数 32 | self.startup_wait_time = config["cad"]["startup_wait_time"] 33 | self.command_delay = config["cad"]["command_delay"] 34 | # 获取CAD类型 35 | self.cad_type = config["cad"]["type"] 36 | # 有效的线宽值列表 37 | self.valid_lineweights = [0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120, 140, 158, 200, 211] 38 | logger.info("CAD控制器已初始化") 39 | 40 | def start_cad(self) -> bool: 41 | """启动CAD并创建或打开一个文档""" 42 | try: 43 | # 初始化COM 44 | pythoncom.CoInitialize() 45 | 46 | # 存储旧实例引用(如果有)以便后续清理 47 | old_app = None 48 | if self.app is not None: 49 | old_app = self.app 50 | self.app = None 51 | self.doc = None 52 | 53 | try: 54 | # 根据配置的CAD类型选择不同的应用程序标识符 55 | app_id = "AutoCAD.Application" 56 | app_name = "AutoCAD" 57 | 58 | if self.cad_type.lower() == "autocad": 59 | app_id = "AutoCAD.Application" 60 | app_name = "AutoCAD" 61 | elif self.cad_type.lower() == "gcad": 62 | app_id = "GCAD.Application" 63 | app_name = "浩辰CAD" 64 | elif self.cad_type.lower() == "gstarcad": 65 | app_id = "GCAD.Application" 66 | app_name = "浩辰CAD" 67 | elif self.cad_type.lower() == "zwcad": 68 | app_id = "ZWCAD.Application" 69 | app_name = "中望CAD" 70 | 71 | # 尝试连接到已运行的CAD实例 72 | logger.info(f"尝试连接现有{app_name}实例...") 73 | try: 74 | self.app = win32com.client.GetActiveObject(app_id) 75 | logger.info(f"成功连接到已运行的{app_name}实例") 76 | except Exception as e: 77 | logger.info(f"未找到运行中的{app_name}实例,将尝试启动新实例: {str(e)}") 78 | raise 79 | 80 | # 已在上面的代码中处理 81 | 82 | # 如果当前没有文档,创建一个新文档 83 | try: 84 | if self.app.Documents.Count == 0: 85 | logger.info("创建新文档...") 86 | self.doc = self.app.Documents.Add() 87 | else: 88 | logger.info("获取活动文档...") 89 | self.doc = self.app.ActiveDocument 90 | except Exception as doc_ex: 91 | # 如果获取文档失败,强制创建新文档 92 | logger.warning(f"获取文档失败,尝试创建新文档: {str(doc_ex)}") 93 | try: 94 | # 关闭所有打开的文档 95 | for i in range(self.app.Documents.Count): 96 | try: 97 | self.app.Documents.Item(0).Close(False) # 不保存 98 | except: 99 | pass 100 | 101 | # 创建新文档 102 | self.doc = self.app.Documents.Add() 103 | except Exception as new_doc_ex: 104 | logger.error(f"创建新文档失败: {str(new_doc_ex)}") 105 | raise 106 | 107 | except Exception as app_ex: 108 | # 如果连接失败,启动一个新实例 109 | logger.info(f"连接失败,正在启动新的CAD实例: {str(app_ex)}") 110 | try: 111 | # 根据配置的CAD类型启动相应的应用程序 112 | app_id = "AutoCAD.Application" 113 | app_name = "AutoCAD" 114 | 115 | if self.cad_type.lower() == "autocad": 116 | app_id = "AutoCAD.Application" 117 | app_name = "AutoCAD" 118 | elif self.cad_type.lower() == "gcad": 119 | app_id = "GCAD.Application" 120 | app_name = "浩辰CAD" 121 | elif self.cad_type.lower() == "gstarcad": 122 | app_id = "GCAD.Application" 123 | app_name = "浩辰CAD" 124 | elif self.cad_type.lower() == "zwcad": 125 | app_id = "ZWCAD.Application" 126 | app_name = "中望CAD" 127 | 128 | logger.info(f"正在启动{app_name}实例...") 129 | self.app = win32com.client.Dispatch(app_id) 130 | self.app.Visible = True 131 | 132 | # 等待CAD启动 133 | time.sleep(self.startup_wait_time) # 使用配置的等待时间 134 | 135 | # 创建新文档 136 | logger.info("尝试创建新文档...") 137 | # self.doc = self.app.Documents.Add() 138 | self.doc = self.app.ActiveDocument 139 | except Exception as new_app_ex: 140 | logger.error(f"启动新CAD实例失败: {str(new_app_ex)}") 141 | raise 142 | 143 | # 额外安全检查和等待 144 | time.sleep(2) # 给CAD更多时间处理文档创建 145 | 146 | if self.doc is None: 147 | raise Exception("无法获取有效的Document对象") 148 | 149 | # 尝试读取文档属性以验证其有效性 150 | try: 151 | name = self.doc.Name 152 | logger.info(f"文档名称: {name}") 153 | except Exception as name_ex: 154 | logger.error(f"无法读取文档名称: {str(name_ex)}") 155 | raise Exception("文档对象无效") 156 | 157 | logger.info("CAD已成功启动和准备") 158 | return True 159 | 160 | except Exception as e: 161 | logger.error(f"启动CAD失败: {str(e)}") 162 | return False 163 | finally: 164 | # 清理旧实例 165 | if old_app is not None: 166 | try: 167 | del old_app 168 | except: 169 | pass 170 | 171 | 172 | def is_running(self) -> bool: 173 | """检查CAD是否正在运行""" 174 | return self.app is not None and self.doc is not None 175 | 176 | def save_drawing(self, file_path: str) -> bool: 177 | """保存当前图纸到指定路径""" 178 | if not self.is_running(): 179 | logger.error("CAD未运行,无法保存图纸") 180 | return False 181 | 182 | try: 183 | # 确保目录存在 184 | os.makedirs(os.path.dirname(file_path), exist_ok=True) 185 | 186 | # 保存文件 187 | self.doc.SaveAs(file_path) 188 | logger.info(f"图纸已保存到: {file_path}") 189 | 190 | return True 191 | except Exception as e: 192 | logger.error(f"保存图纸失败: {str(e)}") 193 | return False 194 | 195 | def refresh_view(self) -> None: 196 | """刷新CAD视图""" 197 | if self.is_running(): 198 | try: 199 | self.doc.Regen(1) # acAllViewports = 1 200 | except Exception as e: 201 | logger.error(f"刷新视图失败: {str(e)}") 202 | 203 | def validate_lineweight(self, lineweight) -> int: 204 | """验证并返回有效的线宽值 205 | 206 | 如果提供的线宽值不在有效值列表中,则返回默认值0 207 | 208 | Args: 209 | lineweight: 要验证的线宽值 210 | 211 | Returns: 212 | 有效的线宽值 213 | """ 214 | if lineweight is None: 215 | return None 216 | 217 | # 检查线宽是否在有效值列表中 218 | if lineweight in self.valid_lineweights: 219 | return lineweight 220 | else: 221 | logger.warning(f"线宽值 {lineweight} 无效,将使用默认值 0") 222 | return 0 223 | 224 | def draw_line(self, start_point: Tuple[float, float, float], 225 | end_point: Tuple[float, float, float], layer: str = None, color: int = None, lineweight=None) -> bool: 226 | """绘制直线""" 227 | if not self.is_running(): 228 | return False 229 | 230 | try: 231 | # 确保点是三维的 232 | if len(start_point) == 2: 233 | start_point = (start_point[0], start_point[1], 0) 234 | if len(end_point) == 2: 235 | end_point = (end_point[0], end_point[1], 0) 236 | 237 | # 使用VARIANT包装坐标点数据 238 | start_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 239 | [start_point[0], start_point[1], start_point[2]]) 240 | end_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 241 | [end_point[0], end_point[1], end_point[2]]) 242 | 243 | # 添加直线 244 | line = self.doc.ModelSpace.AddLine(start_array, end_array) 245 | 246 | # 如果指定了图层,设置图层 247 | if layer: 248 | # 确保图层存在 249 | self.create_layer(layer) 250 | # 设置实体的图层 251 | line.Layer = layer 252 | 253 | # 如果指定了颜色,设置颜色 254 | if color is not None: 255 | line.Color = color 256 | 257 | if lineweight is not None: 258 | line.LineWeight = self.validate_lineweight(lineweight) 259 | 260 | # 刷新视图 261 | self.refresh_view() 262 | 263 | logger.debug(f"已绘制直线: 起点{start_point}, 终点{end_point}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 264 | return line 265 | 266 | except Exception as e: 267 | logger.error(f"绘制直线时出错: {str(e)}") 268 | return None 269 | 270 | def draw_circle(self, center: Tuple[float, float, float], 271 | radius: float, layer: str = None, color: int = None, lineweight=None) -> Any: 272 | """绘制圆""" 273 | if not self.is_running(): 274 | return None 275 | 276 | try: 277 | # 确保点是三维的 278 | if len(center) == 2: 279 | center = (center[0], center[1], 0) 280 | 281 | # 使用VARIANT包装坐标点数据 282 | center_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 283 | [center[0], center[1], center[2]]) 284 | 285 | # 添加圆 286 | circle = self.doc.ModelSpace.AddCircle(center_array, radius) 287 | 288 | # 如果指定了图层,设置图层 289 | if layer: 290 | # 确保图层存在 291 | self.create_layer(layer) 292 | # 设置实体的图层 293 | circle.Layer = layer 294 | 295 | # 如果指定了颜色,设置颜色 296 | if color is not None: 297 | circle.Color = color 298 | 299 | if lineweight is not None: 300 | circle.LineWeight = self.validate_lineweight(lineweight) 301 | 302 | # 刷新视图 303 | self.refresh_view() 304 | 305 | logger.debug(f"已绘制圆: 中心{center}, 半径{radius}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 306 | return circle 307 | 308 | except Exception as e: 309 | logger.error(f"绘制圆时出错: {str(e)}") 310 | return None 311 | 312 | def draw_arc(self, center: Tuple[float, float, float], 313 | radius: float, start_angle: float, end_angle: float, layer: str = None, color: int = None, lineweight=None) -> Any: 314 | """绘制圆弧""" 315 | if not self.is_running(): 316 | return None 317 | 318 | try: 319 | # 确保点是三维的 320 | if len(center) == 2: 321 | center = (center[0], center[1], 0) 322 | 323 | # 将角度转换为弧度 324 | start_rad = math.radians(start_angle) 325 | end_rad = math.radians(end_angle) 326 | 327 | # 使用VARIANT包装坐标点数据 328 | center_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 329 | [center[0], center[1], center[2]]) 330 | 331 | # 添加圆弧 332 | arc = self.doc.ModelSpace.AddArc(center_array, radius, start_rad, end_rad) 333 | 334 | # 如果指定了图层,设置图层 335 | if layer: 336 | # 确保图层存在 337 | self.create_layer(layer) 338 | # 设置实体的图层 339 | arc.Layer = layer 340 | 341 | # 如果指定了颜色,设置颜色 342 | if color is not None: 343 | arc.Color = color 344 | 345 | if lineweight is not None: 346 | arc.LineWeight = self.validate_lineweight(lineweight) 347 | 348 | # 刷新视图 349 | self.refresh_view() 350 | 351 | logger.debug(f"已绘制圆弧: 中心{center}, 半径{radius}, 起始角度{start_angle}, 结束角度{end_angle}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 352 | return arc 353 | except Exception as e: 354 | logger.error(f"绘制圆弧失败: {str(e)}") 355 | return None 356 | 357 | def draw_ellipse(self, center: Tuple[float, float, float], 358 | major_axis: float, minor_axis: float, rotation: float = 0, 359 | layer: str = None, color: int = None, lineweight=None) -> Any: 360 | """绘制椭圆""" 361 | if not self.is_running(): 362 | return None 363 | 364 | try: 365 | # 确保点是三维的 366 | if len(center) == 2: 367 | center = (center[0], center[1], 0) 368 | 369 | if rotation is None: 370 | rotation = 0 371 | 372 | # 将旋转角度转换为弧度 373 | rotation_rad = math.radians(rotation) 374 | 375 | # 使用VARIANT包装坐标点数据 376 | center_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 377 | [center[0], center[1], center[2]]) 378 | 379 | # 计算椭圆的主轴向量 380 | major_x = major_axis * math.cos(rotation_rad) 381 | major_y = major_axis * math.sin(rotation_rad) 382 | major_vector = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 383 | [major_x, major_y, 0]) 384 | 385 | # 添加椭圆 386 | ellipse = self.doc.ModelSpace.AddEllipse(center_array, major_vector, minor_axis / major_axis) 387 | 388 | # 如果指定了图层,设置图层 389 | if layer: 390 | # 确保图层存在 391 | self.create_layer(layer) 392 | # 设置实体的图层 393 | ellipse.Layer = layer 394 | 395 | # 如果指定了颜色,设置颜色 396 | if color is not None: 397 | ellipse.Color = color 398 | 399 | if lineweight is not None: 400 | ellipse.LineWeight = self.validate_lineweight(lineweight) 401 | 402 | # 刷新视图 403 | self.refresh_view() 404 | 405 | logger.debug(f"已绘制椭圆: 中心{center}, 长轴{major_axis}, 短轴{minor_axis}, 旋转角度{rotation}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 406 | return ellipse 407 | except Exception as e: 408 | logger.error(f"绘制椭圆失败: {str(e)}") 409 | return None 410 | 411 | def draw_polyline(self, points: List[Tuple[float, float, float]], closed: bool = False, layer: str = None, color: int = None, lineweight=None) -> Any: 412 | """绘制多段线""" 413 | if not self.is_running(): 414 | return None 415 | 416 | try: 417 | # 确保所有点都是三维的 418 | processed_points = [] 419 | for point in points: 420 | if len(point) == 2: 421 | processed_points.append((point[0], point[1], 0)) 422 | else: 423 | processed_points.append(point) 424 | 425 | # 创建点数组 426 | point_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 427 | [coord for point in processed_points for coord in point]) 428 | 429 | # 添加多段线 430 | polyline = self.doc.ModelSpace.AddPolyline(point_array) 431 | 432 | # 如果需要闭合 433 | if closed and len(processed_points) > 2: 434 | polyline.Closed = True 435 | 436 | # 如果指定了图层,设置图层 437 | if layer: 438 | # 确保图层存在 439 | self.create_layer(layer) 440 | # 设置实体的图层 441 | polyline.Layer = layer 442 | 443 | # 如果指定了颜色,设置颜色 444 | if color is not None: 445 | polyline.Color = color 446 | 447 | if lineweight is not None: 448 | polyline.LineWeight = self.validate_lineweight(lineweight) 449 | 450 | # 刷新视图 451 | self.refresh_view() 452 | 453 | logger.debug(f"已绘制多段线: {len(points)}个点, {'闭合' if closed else '不闭合'}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 454 | return polyline 455 | except Exception as e: 456 | logger.error(f"绘制多段线时出错: {str(e)}") 457 | return None 458 | 459 | def draw_rectangle(self, corner1: Tuple[float, float, float], 460 | corner2: Tuple[float, float, float], layer: str = None, color: int = None, lineweight=None) -> Any: 461 | """绘制矩形""" 462 | if not self.is_running(): 463 | return None 464 | 465 | try: 466 | # 确保点是三维的 467 | if len(corner1) == 2: 468 | corner1 = (corner1[0], corner1[1], 0) 469 | if len(corner2) == 2: 470 | corner2 = (corner2[0], corner2[1], 0) 471 | 472 | # 计算矩形的四个角点 473 | x1, y1, z1 = corner1 474 | x2, y2, z2 = corner2 475 | 476 | # 创建矩形的四个点 477 | points = [ 478 | (x1, y1, z1), 479 | (x2, y1, z1), 480 | (x2, y2, z1), 481 | (x1, y2, z1), 482 | (x1, y1, z1) # 闭合矩形 483 | ] 484 | 485 | # 使用多段线绘制矩形 486 | return self.draw_polyline(points, True, layer, color, lineweight) 487 | except Exception as e: 488 | logger.error(f"绘制矩形时出错: {str(e)}") 489 | return None 490 | 491 | def draw_text(self, position: Tuple[float, float, float], 492 | text: str, height: float = 2.5, rotation: float = 0, layer: str = None, color: int = None) -> Any: 493 | """添加文本""" 494 | if not self.is_running(): 495 | return None 496 | 497 | try: 498 | # 确保点是三维的 499 | if len(position) == 2: 500 | position = (position[0], position[1], 0) 501 | 502 | # 使用VARIANT包装坐标点数据 503 | position_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 504 | [position[0], position[1], position[2]]) 505 | 506 | # 添加文本 507 | text_obj = self.doc.ModelSpace.AddText(text, position_array, height) 508 | 509 | # 设置旋转角度 510 | if rotation != 0: 511 | text_obj.Rotation = math.radians(rotation) 512 | 513 | # 如果指定了图层,设置图层 514 | if layer: 515 | # 确保图层存在 516 | self.create_layer(layer) 517 | # 设置实体的图层 518 | text_obj.Layer = layer 519 | 520 | # 如果指定了颜色,设置颜色 521 | if color is not None: 522 | text_obj.Color = color 523 | 524 | # 刷新视图 525 | self.refresh_view() 526 | 527 | logger.debug(f"已添加文本: '{text}', 位置{position}, 高度{height}, 旋转{rotation}度, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 528 | return text_obj 529 | except Exception as e: 530 | logger.error(f"添加文本时出错: {str(e)}") 531 | return None 532 | 533 | def draw_hatch(self, points: List[Tuple[float, float, float]], 534 | pattern_name: str = "SOLID", scale: float = 1.0, layer: str = None, color: int = None) -> Any: 535 | """绘制填充图案 536 | 537 | Args: 538 | points: 填充边界的点集,每个点为二维或三维坐标元组 539 | pattern_name: 填充图案名称,默认为"SOLID"(实体填充) 540 | scale: 填充图案比例,默认为1.0 541 | layer: 图层名称,如果为None则使用当前图层 542 | color: 颜色索引,如果为None则使用默认颜色 543 | 544 | Returns: 545 | 成功返回填充对象,失败返回None 546 | """ 547 | if not self.is_running(): 548 | return None 549 | 550 | try: 551 | # 确保所有点都是有效的 552 | if not points or len(points) < 3: 553 | logger.error("创建填充失败: 至少需要3个点来定义填充边界") 554 | return None 555 | 556 | # 创建闭合多段线作为边界 557 | closed_polyline = self.draw_polyline(points, closed=True, layer=layer) 558 | if not closed_polyline: 559 | logger.error("创建填充失败: 无法创建边界多段线") 560 | return None 561 | 562 | # 创建填充对象 (0表示正常填充,True表示关联边界) 563 | hatch = self.doc.ModelSpace.AddHatch(0, pattern_name, True) 564 | 565 | # 添加外部边界循环 566 | # 使用VARIANT包装对象数组 567 | object_ids = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_DISPATCH, [closed_polyline]) 568 | hatch.AppendOuterLoop(object_ids) 569 | 570 | # 设置填充图案比例 571 | hatch.PatternScale = scale 572 | 573 | # 如果指定了图层,设置图层 574 | if layer: 575 | # 确保图层存在 576 | self.create_layer(layer) 577 | # 设置实体的图层 578 | hatch.Layer = layer 579 | 580 | # 如果指定了颜色,设置颜色 581 | if color is not None: 582 | hatch.Color = color 583 | 584 | # 更新填充 (计算填充区域) 585 | hatch.Evaluate() 586 | 587 | # 刷新视图 588 | self.refresh_view() 589 | 590 | logger.debug(f"已创建填充: 图案 {pattern_name}, 比例 {scale}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") 591 | return hatch 592 | except Exception as e: 593 | logger.error(f"创建填充时出错: {str(e)}") 594 | return None 595 | 596 | def zoom_extents(self) -> bool: 597 | """缩放视图以显示所有对象""" 598 | if not self.is_running(): 599 | return False 600 | 601 | try: 602 | self.doc.ActiveViewport.ZoomExtents() 603 | logger.info("已缩放视图以显示所有对象") 604 | return True 605 | except Exception as e: 606 | logger.error(f"缩放视图时出错: {str(e)}") 607 | return False 608 | 609 | def close(self) -> None: 610 | """关闭CAD控制器""" 611 | try: 612 | # 释放COM资源 613 | if self.app is not None: 614 | del self.app 615 | pythoncom.CoUninitialize() 616 | except: 617 | pass 618 | 619 | 620 | def create_layer(self, layer_name: str) -> bool: # , color: Union[int, Tuple[int, int, int]] = 7 621 | """创建新图层 622 | 623 | Args: 624 | layer_name: 图层名称 625 | color: 颜色值,可以是CAD颜色索引(int)或RGB颜色值(tuple) 626 | 627 | Returns: 628 | 操作是否成功 629 | """ 630 | if not self.is_running(): 631 | return False 632 | 633 | try: 634 | # 检查图层是否已存在 635 | for i in range(self.doc.Layers.Count): 636 | if self.doc.Layers.Item(i).Name == layer_name: 637 | # 图层已存在,激活它 638 | self.doc.ActiveLayer = self.doc.Layers.Item(i) 639 | return True 640 | 641 | # 创建新图层 642 | new_layer = self.doc.Layers.Add(layer_name) 643 | 644 | # 图层不设置颜色,设置里面的实体颜色 645 | # # 设置颜色 646 | # if isinstance(color, int): 647 | # # 使用颜色索引 648 | # new_layer.Color = color 649 | # elif isinstance(color, tuple) and len(color) == 3: 650 | # # 使用RGB值 651 | # r, g, b = color 652 | # # 设置TrueColor 653 | # new_layer.TrueColor = self._create_true_color(r, g, b) 654 | 655 | # 设置为当前图层 656 | self.doc.ActiveLayer = new_layer 657 | logger.info(f"已创建新图层: {layer_name}") #, 颜色: {color} 658 | return True 659 | except Exception as e: 660 | logger.error(f"创建图层时出错: {str(e)}") 661 | return False 662 | 663 | def add_dimension(self, start_point: Tuple[float, float, float], 664 | end_point: Tuple[float, float, float], 665 | text_position: Tuple[float, float, float] = None, textheight: float = 5,layer: str = None, color: int=None) -> Any: 666 | """添加线性标注""" 667 | if not self.is_running(): 668 | return None 669 | 670 | try: 671 | # 确保点是三维的 672 | if len(start_point) == 2: 673 | start_point = (start_point[0], start_point[1], 0) 674 | if len(end_point) == 2: 675 | end_point = (end_point[0], end_point[1], 0) 676 | 677 | # 如果未提供文本位置,自动计算 678 | if text_position is None: 679 | # 在起点和终点之间的中点上方 680 | mid_x = (start_point[0] + end_point[0]) / 2 681 | mid_y = (start_point[1] + end_point[1]) / 2 682 | text_position = (mid_x, mid_y + 5, 0) 683 | elif len(text_position) == 2: 684 | text_position = (text_position[0], text_position[1], 0) 685 | 686 | # 使用VARIANT包装坐标点数据 687 | start_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 688 | [start_point[0], start_point[1], start_point[2]]) 689 | end_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 690 | [end_point[0], end_point[1], end_point[2]]) 691 | text_pos_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, 692 | [text_position[0], text_position[1], text_position[2]]) 693 | 694 | # 添加对齐标注 695 | dimension = self.doc.ModelSpace.AddDimAligned(start_array, end_array, text_pos_array) 696 | 697 | # 设置文字高度 698 | if textheight is not None: 699 | dimension.TextHeight = textheight 700 | 701 | # 如果指定了图层,设置图层 702 | if layer: 703 | # 确保图层存在 704 | self.create_layer(layer) 705 | # 设置实体的图层 706 | dimension.Layer = layer 707 | 708 | # 如果指定了颜色,设置颜色 709 | if color is not None: 710 | dimension.Color = color 711 | 712 | # 刷新视图 713 | self.refresh_view() 714 | 715 | logger.info(f"已添加标注: 从 {start_point} 到 {end_point}, 图层{layer if layer else '默认'}") 716 | return dimension 717 | except Exception as e: 718 | logger.error(f"添加标注时出错: {str(e)}") 719 | return None 720 | 721 | -------------------------------------------------------------------------------- /src/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import logging 5 | from pathlib import Path 6 | from mcp.server.models import InitializationOptions 7 | import mcp.types as types 8 | from mcp.server import NotificationOptions, Server 9 | import mcp.server.stdio 10 | from pydantic import AnyUrl 11 | from typing import Any, Dict, List 12 | import sys 13 | 14 | sys.dont_write_bytecode = True 15 | 16 | # 使用绝对导入 17 | 18 | from nlp_processor import NLPProcessor 19 | 20 | from cad_controller import CADController 21 | 22 | 23 | # 配置Windows环境下的UTF-8编码 24 | if sys.platform == "win32" and os.environ.get('PYTHONIOENCODING') is None: 25 | sys.stdin.reconfigure(encoding="utf-8") 26 | sys.stdout.reconfigure(encoding="utf-8") 27 | sys.stderr.reconfigure(encoding="utf-8") 28 | 29 | # 配置日志 30 | logging.basicConfig( 31 | level=logging.INFO, 32 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 33 | handlers=[ 34 | logging.StreamHandler(), 35 | logging.FileHandler('cad_mcp.log', encoding='utf-8') 36 | ] 37 | ) 38 | 39 | 40 | logger = logging.getLogger('mcp_cad_server') 41 | logger.info("启动 CAD MCP 服务器") 42 | 43 | 44 | PROMPT_TEMPLATE = """ 45 | 助手的目标是演示如何使用CAD MCP服务。通过这个服务,您可以直接在对话中控制CAD软件,创建和修改图形。 46 | 47 | 48 | 49 | 提示: 50 | 这个服务器提供了一个名为"cad-assistant"的预设提示,帮助用户通过自然语言指令控制CAD。用户可以要求绘制各种形状、修改图形元素或保存图纸。 51 | 52 | 资源: 53 | 此服务器提供"drawing://current"资源,表示当前的CAD图纸状态。 54 | 55 | 工具: 56 | 57 | 此服务器提供以下基本绘图工具: 58 | "draw_line": 在CAD中绘制直线 59 | "draw_circle": 在CAD中绘制圆 60 | "draw_arc": 在CAD中绘制弧 61 | "draw_ellipse": 在CAD中绘制椭圆 62 | "draw_polyline": 在CAD中绘制多段线 63 | "draw_rectangle": 在CAD中绘制矩形 64 | "draw_text": 在CAD中添加文本 65 | "draw_hatch": 在CAD中绘制填充 66 | "save_drawing": 保存当前图纸 67 | "add_dimension": 在CAD中添加线性标注 68 | "process_command": 处理自然语言命令并转换为CAD操作 69 | 70 | 71 | 72 | 请以友好的方式开始演示,例如:"嗨!今天我将向您展示如何使用CAD MCP服务。通过这个服务,您可以直接在我们的对话中控制CAD软件,无需手动操作界面。让我们开始吧!" 73 | """ 74 | 75 | # 后期再做 76 | # "erase_entity": 删除指定的实体 77 | # "move_entity": 移动指定的实体 78 | # "rotate_entity": 旋转指定的实体 79 | # "scale_entity": 缩放指定的实体 80 | 81 | class Config: 82 | 83 | def __init__(self): 84 | # 直接读取config.json文件 85 | config_path = os.path.join(os.path.dirname(__file__), 'config.json') 86 | with open(config_path, 'r', encoding='utf-8') as f: 87 | self.config = json.load(f) 88 | logger.info("配置文件加载成功") 89 | 90 | @property 91 | def server_name(self) -> str: 92 | return self.config['server']['name'] 93 | 94 | @property 95 | def server_version(self) -> str: 96 | return self.config['server']['version'] 97 | 98 | class CADService: 99 | def __init__(self): 100 | """初始化CAD服务""" 101 | self.controller = CADController() 102 | self.nlp_processor = NLPProcessor() 103 | self.drawing_state = { 104 | "entities": [], 105 | "current_layer": "0", 106 | "last_command": "", 107 | "last_result": "" 108 | } 109 | logger.info("CAD服务已初始化") 110 | 111 | def start_cad(self): 112 | """启动CAD""" 113 | return self.controller.start_cad() 114 | 115 | def draw_line(self, start_point, end_point, layer=None, color=None, lineweight=None): 116 | """绘制直线""" 117 | if not self.controller.is_running(): 118 | self.start_cad() 119 | 120 | # 使用当前图层或指定图层 121 | current_layer = layer or self.drawing_state["current_layer"] 122 | 123 | result = self.controller.draw_line(start_point, end_point, current_layer, color, lineweight) 124 | if result: 125 | self.drawing_state["entities"].append({ 126 | "type": "line", 127 | "start": start_point, 128 | "end": end_point, 129 | "layer": current_layer, 130 | "color": color, 131 | "lineweight": lineweight 132 | }) 133 | self.drawing_state["last_command"] = f"绘制直线从{start_point}到{end_point}" 134 | self.drawing_state["last_result"] = "成功" 135 | else: 136 | self.drawing_state["last_result"] = "失败" 137 | 138 | return result 139 | 140 | def draw_circle(self, center, radius, layer=None, color=None, lineweight=None): 141 | """绘制圆""" 142 | if not self.controller.is_running(): 143 | self.start_cad() 144 | 145 | # 使用当前图层或指定图层 146 | current_layer = layer or self.drawing_state["current_layer"] 147 | 148 | result = self.controller.draw_circle(center, radius, current_layer, color, lineweight) 149 | if result: 150 | self.drawing_state["entities"].append({ 151 | "type": "circle", 152 | "center": center, 153 | "radius": radius, 154 | "layer": current_layer, 155 | "color": color, 156 | "lineweight": lineweight 157 | }) 158 | self.drawing_state["last_command"] = f"绘制圆,中心点{center},半径{radius},图层{current_layer}" 159 | self.drawing_state["last_result"] = "成功" 160 | else: 161 | self.drawing_state["last_result"] = "失败" 162 | 163 | return result 164 | 165 | def draw_arc(self, center, radius, start_angle, end_angle, layer=None, color=None, lineweight=None): 166 | """绘制弧""" 167 | if not self.controller.is_running(): 168 | self.start_cad() 169 | 170 | # 使用当前图层或指定图层 171 | current_layer = layer or self.drawing_state["current_layer"] 172 | 173 | result = self.controller.draw_arc(center, radius, start_angle, end_angle, current_layer, color, lineweight) 174 | 175 | if result: 176 | self.drawing_state["entities"].append({ 177 | "type": "arc", 178 | "center": center, 179 | "radius": radius, 180 | "start_angle": start_angle, 181 | "end_angle": end_angle, 182 | "layer": current_layer, 183 | "color": color, 184 | "lineweight": lineweight 185 | }) 186 | self.drawing_state["last_command"] = f"绘制弧,中心点{center},半径{radius},起始角度{start_angle},结束角度{end_angle},图层{current_layer}" 187 | self.drawing_state["last_result"] = "成功" 188 | else: 189 | self.drawing_state["last_result"] = "失败" 190 | 191 | return result 192 | 193 | def draw_ellipse(self, center, major_axis, minor_axis, rotation=0, layer=None, color=None, lineweight=None): 194 | """绘制椭圆""" 195 | if not self.controller.is_running(): 196 | self.start_cad() 197 | 198 | # 使用当前图层或指定图层 199 | current_layer = layer or self.drawing_state["current_layer"] 200 | 201 | result = self.controller.draw_ellipse(center, major_axis, minor_axis, rotation, current_layer, color, lineweight) 202 | 203 | if result: 204 | self.drawing_state["entities"].append({ 205 | "type": "ellipse", 206 | "center": center, 207 | "major_axis": major_axis, 208 | "minor_axis": minor_axis, 209 | "rotation": rotation, 210 | "layer": current_layer, 211 | "color": color, 212 | "lineweight": lineweight 213 | }) 214 | self.drawing_state["last_command"] = f"绘制椭圆,中心点{center},长轴{major_axis},短轴{minor_axis},旋转角度{rotation},图层{current_layer}" 215 | self.drawing_state["last_result"] = "成功" 216 | else: 217 | self.drawing_state["last_result"] = "失败" 218 | 219 | return result 220 | 221 | def draw_polyline(self, points, closed=False, layer=None, color=None, lineweight=None): 222 | """绘制多段线""" 223 | if not self.controller.is_running(): 224 | self.start_cad() 225 | 226 | # 使用当前图层或指定图层 227 | current_layer = layer or self.drawing_state["current_layer"] 228 | 229 | result = self.controller.draw_polyline(points, closed, current_layer, color, lineweight) 230 | if result: 231 | self.drawing_state["entities"].append({ 232 | "type": "polyline", 233 | "points": points, 234 | "closed": closed, 235 | "layer": current_layer, 236 | "color": color, 237 | "lineweight": lineweight 238 | }) 239 | self.drawing_state["last_command"] = f"绘制多段线,点集{points},{'闭合' if closed else '不闭合'},图层{current_layer}" 240 | self.drawing_state["last_result"] = "成功" 241 | else: 242 | self.drawing_state["last_result"] = "失败" 243 | 244 | return result 245 | 246 | def draw_rectangle(self, corner1, corner2, layer=None, color=None, lineweight=None): 247 | """绘制矩形""" 248 | if not self.controller.is_running(): 249 | self.start_cad() 250 | 251 | # 使用当前图层或指定图层 252 | current_layer = layer or self.drawing_state["current_layer"] 253 | 254 | result = self.controller.draw_rectangle(corner1, corner2, current_layer, color, lineweight) 255 | if result: 256 | self.drawing_state["entities"].append({ 257 | "type": "rectangle", 258 | "corner1": corner1, 259 | "corner2": corner2, 260 | "layer": current_layer, 261 | "color": color, 262 | "lineweight": lineweight 263 | }) 264 | self.drawing_state["last_command"] = f"绘制矩形,对角点{corner1}和{corner2},图层{current_layer}" 265 | self.drawing_state["last_result"] = "成功" 266 | else: 267 | self.drawing_state["last_result"] = "失败" 268 | 269 | return result 270 | 271 | def draw_text(self, position, text, height=2.5, rotation=0, layer=None, color=None): 272 | """添加文本""" 273 | if not self.controller.is_running(): 274 | self.start_cad() 275 | 276 | # 使用当前图层或指定图层 277 | current_layer = layer or self.drawing_state["current_layer"] 278 | 279 | result = self.controller.draw_text(position, text, height, rotation, current_layer, color) 280 | if result: 281 | self.drawing_state["entities"].append({ 282 | "type": "text", 283 | "position": position, 284 | "text": text, 285 | "height": height, 286 | "rotation": rotation, 287 | "layer": current_layer, 288 | "color": color 289 | }) 290 | self.drawing_state["last_command"] = f"添加文本'{text}',位置{position},高度{height},旋转{rotation}" 291 | self.drawing_state["last_result"] = "成功" 292 | else: 293 | self.drawing_state["last_result"] = "失败" 294 | 295 | return result 296 | 297 | def draw_hatch(self, points, pattern_name="SOLID", scale=1.0, layer=None, color=None): 298 | """绘制填充""" 299 | if not self.controller.is_running(): 300 | self.start_cad() 301 | 302 | # 使用当前图层或指定图层 303 | current_layer = layer or self.drawing_state["current_layer"] 304 | 305 | result = self.controller.draw_hatch(points, pattern_name, scale, current_layer, color) 306 | if result: 307 | self.drawing_state["entities"].append({ 308 | "type": "hatch", 309 | "points": points, 310 | "pattern_name": pattern_name, 311 | "scale": scale, 312 | "layer": current_layer, 313 | "color": color 314 | }) 315 | self.drawing_state["last_command"] = f"绘制填充,点集{points},图案{pattern_name},比例{scale},图层{current_layer}" 316 | self.drawing_state["last_result"] = "成功" 317 | else: 318 | self.drawing_state["last_result"] = "失败" 319 | 320 | return result 321 | 322 | def add_dimension(self, start_point, end_point, text_position=None,textheight=5, layer=None, color=None): 323 | """添加线性标注""" 324 | if not self.controller.is_running(): 325 | self.start_cad() 326 | 327 | # 使用当前图层或指定图层 328 | current_layer = layer or self.drawing_state["current_layer"] 329 | 330 | result = self.controller.add_dimension(start_point, end_point, text_position, textheight,current_layer, color) 331 | if result: 332 | self.drawing_state["entities"].append({ 333 | "type": "dimension", 334 | "start": start_point, 335 | "end": end_point, 336 | "text_position": text_position, 337 | "textheight": textheight, 338 | "layer": current_layer, 339 | "color": color 340 | }) 341 | self.drawing_state["last_command"] = f"添加标注从{start_point}到{end_point}" 342 | self.drawing_state["last_result"] = "成功" 343 | else: 344 | self.drawing_state["last_result"] = "失败" 345 | 346 | return result 347 | 348 | 349 | def save_drawing(self, file_path): 350 | """保存图纸""" 351 | if not self.controller.is_running(): 352 | return False 353 | 354 | result = self.controller.save_drawing(file_path) 355 | if result: 356 | self.drawing_state["last_command"] = f"保存图纸到{file_path}" 357 | self.drawing_state["last_result"] = "成功" 358 | else: 359 | self.drawing_state["last_result"] = "失败" 360 | 361 | return result 362 | 363 | def process_command(self, command: str) -> Dict[str, Any]: 364 | """处理自然语言命令""" 365 | if not self.controller.is_running(): 366 | self.start_cad() 367 | # 使用NLP处理器解析命令 368 | parsed_command = self.nlp_processor.process_command(command) 369 | command_type = parsed_command.get("type") 370 | try: 371 | # 基本绘图命令处理 372 | if command_type == "draw_line": 373 | start_point = parsed_command.get("start_point") 374 | end_point = parsed_command.get("end_point") 375 | # 获取颜色参数 376 | color = parsed_command.get("color") 377 | # 尝试从命令中提取颜色名称并转换为RGB值 378 | color_rgb = self.nlp_processor.extract_color_from_command(command) 379 | if color_rgb is not None: 380 | color = color_rgb # 优先使用从命令中提取的颜色 381 | # 获取线宽参数 382 | lineweight = parsed_command.get("lineweight") 383 | result = self.draw_line(start_point, end_point, None, color, lineweight) 384 | return { 385 | "success": result is not None, 386 | "message": "直线已绘制" if result else "绘制直线失败", 387 | "entity_id": result.Handle if result else None 388 | } 389 | 390 | elif command_type == "draw_circle": 391 | center = parsed_command.get("center") 392 | radius = parsed_command.get("radius") 393 | # 获取颜色参数 394 | color = parsed_command.get("color") 395 | # 尝试从命令中提取颜色名称并转换为RGB值 396 | color_rgb = self.nlp_processor.extract_color_from_command(command) 397 | if color_rgb is not None: 398 | color = color_rgb # 优先使用从命令中提取的颜色 399 | # 获取线宽参数 400 | lineweight = parsed_command.get("lineweight") 401 | result = self.draw_circle(center, radius, None, color, lineweight) 402 | return { 403 | "success": result is not None, 404 | "message": "圆已绘制" if result else "绘制圆失败", 405 | "entity_id": result.Handle if result else None 406 | } 407 | 408 | elif command_type == "draw_arc": 409 | center = parsed_command.get("center") 410 | radius = parsed_command.get("radius") 411 | start_angle = parsed_command.get("start_angle") 412 | end_angle = parsed_command.get("end_angle") 413 | # 确保所有必要参数都存在且有效 414 | if center is None or radius is None or start_angle is None or end_angle is None: 415 | return { 416 | "success": False, 417 | "message": "绘制圆弧失败:缺少必要参数", 418 | "error": "缺少必要参数:中心点、半径、起始角度或结束角度" 419 | } 420 | # 获取颜色参数 421 | color = parsed_command.get("color") 422 | # 尝试从命令中提取颜色名称并转换为RGB值 423 | color_rgb = self.nlp_processor.extract_color_from_command(command) 424 | if color_rgb is not None: 425 | color = color_rgb # 优先使用从命令中提取的颜色 426 | # 获取线宽参数 427 | lineweight = parsed_command.get("lineweight") 428 | result = self.draw_arc(center, radius, start_angle, end_angle, None, color, lineweight) 429 | 430 | return { 431 | "success": result is not None, 432 | "message": "圆弧已绘制" if result else "绘制圆弧失败", 433 | "entity_id": result.Handle if result else None 434 | } 435 | 436 | elif command_type == "draw_ellipse": 437 | center = parsed_command.get("center") 438 | major_axis = parsed_command.get("major_axis") 439 | minor_axis = parsed_command.get("minor_axis") 440 | rotation = parsed_command.get("rotation", 0) # 默认旋转角度为0 441 | # 确保所有必要参数都存在且有效 442 | if center is None or major_axis is None or minor_axis is None: 443 | return { 444 | "success": False, 445 | "message": "绘制椭圆失败:缺少必要参数", 446 | "error": "缺少必要参数:中心点、长轴或短轴" 447 | } 448 | # 获取颜色参数 449 | color = parsed_command.get("color") 450 | # 尝试从命令中提取颜色名称并转换为RGB值 451 | color_rgb = self.nlp_processor.extract_color_from_command(command) 452 | if color_rgb is not None: 453 | color = color_rgb # 优先使用从命令中提取的颜色 454 | # 获取线宽参数 455 | lineweight = parsed_command.get("lineweight") 456 | result = self.draw_ellipse(center, major_axis, minor_axis, rotation, None, color, lineweight) 457 | 458 | return { 459 | "success": result is not None, 460 | "message": "椭圆已绘制" if result else "绘制椭圆失败", 461 | "entity_id": result.Handle if result else None 462 | } 463 | 464 | elif command_type == "draw_rectangle": 465 | corner1 = parsed_command.get("corner1") 466 | corner2 = parsed_command.get("corner2") 467 | # 获取颜色参数 468 | color = parsed_command.get("color") 469 | # 尝试从命令中提取颜色名称并转换为RGB值 470 | color_rgb = self.nlp_processor.extract_color_from_command(command) 471 | if color_rgb is not None: 472 | color = color_rgb # 优先使用从命令中提取的颜色 473 | # 获取线宽参数 474 | lineweight = parsed_command.get("lineweight") 475 | result = self.draw_rectangle(corner1, corner2, None, color, lineweight) 476 | 477 | return { 478 | "success": result is not None, 479 | "message": "矩形已绘制" if result else "绘制矩形失败", 480 | "entity_id": result.Handle if result else None 481 | } 482 | 483 | elif command_type == "draw_text": 484 | position = parsed_command.get("position") 485 | text = parsed_command.get("text") 486 | height = parsed_command.get("height", 2.5) 487 | rotation = parsed_command.get("rotation", 0) 488 | # 获取颜色参数 489 | color = parsed_command.get("color") 490 | # 尝试从命令中提取颜色名称并转换为RGB值 491 | color_rgb = self.nlp_processor.extract_color_from_command(command) 492 | if color_rgb is not None: 493 | color = color_rgb # 优先使用从命令中提取的颜色 494 | result = self.draw_text(position, text, height, rotation, None, color) 495 | 496 | return { 497 | "success": result is not None, 498 | "message": "文本已添加" if result else "添加文本失败", 499 | "entity_id": result.Handle if result else None 500 | } 501 | 502 | elif command_type == "draw_hatch": 503 | points = parsed_command.get("points") 504 | pattern_name = parsed_command.get("pattern_name", "SOLID") 505 | scale = parsed_command.get("scale", 1.0) 506 | # 确保所有必要参数都存在且有效 507 | if points is None or len(points) < 3: 508 | return { 509 | "success": False, 510 | "message": "绘制填充失败:缺少必要参数或点数不足", 511 | "error": "填充边界至少需要3个点" 512 | } 513 | # 获取颜色参数,可能是索引或RGB值 514 | color = parsed_command.get("color") # 获取颜色参数 515 | # 尝试从命令中提取颜色名称并转换为RGB值 516 | color_rgb = self.nlp_processor.extract_color_from_command(command) 517 | if color_rgb is not None: 518 | color = color_rgb # 优先使用从命令中提取的颜色 519 | result = self.draw_hatch(points, pattern_name, scale, None, color) 520 | 521 | return { 522 | "success": result is not None, 523 | "message": "填充已绘制" if result else "绘制填充失败", 524 | "entity_id": result.Handle if result else None 525 | } 526 | 527 | # 处理标注 528 | elif command_type == "add_dimension": 529 | start_point = parsed_command.get("start_point") 530 | end_point = parsed_command.get("end_point") 531 | text_position = parsed_command.get("text_position") 532 | result = self.controller.add_dimension(start_point, end_point, text_position) 533 | return { 534 | "success": result is not None, 535 | "message": "标注已添加" if result else "添加标注失败", 536 | "entity_id": result.Handle if result else None 537 | } 538 | 539 | elif command_type == "save": 540 | file_path = parsed_command.get("file_path") 541 | result = self.save_drawing(file_path) 542 | 543 | return { 544 | "success": result, 545 | "message": f"图纸已保存到 {file_path}" if result else f"保存图纸到 {file_path} 失败" 546 | } 547 | 548 | 549 | # 处理图层操作 550 | elif command_type == "create_layer": 551 | layer_name = parsed_command.get("layer_name") 552 | color = parsed_command.get("color", 7) # 默认白色 553 | result = self.controller.create_layer(layer_name) # , color 554 | return { 555 | "success": result, 556 | "message": f"图层 {layer_name} 已创建" if result else f"创建图层 {layer_name} 失败" 557 | } 558 | 559 | except Exception as e: 560 | return { 561 | "success": False, 562 | "message": f"处理命令时出错: {str(e)}" 563 | } 564 | 565 | async def main(): 566 | """主入口函数""" 567 | logger.info("启动 CAD MCP 服务器") 568 | 569 | # 加载配置 570 | config = Config() 571 | cad_service = CADService() 572 | server = Server(config.server_name) 573 | 574 | # 注册处理程序 575 | logger.debug("注册处理程序") 576 | 577 | @server.list_resources() 578 | async def handle_list_resources() -> list[types.Resource]: 579 | logger.debug("处理 list_resources 请求") 580 | return [ 581 | types.Resource( 582 | uri=AnyUrl("drawing://current"), 583 | name="当前CAD图纸", 584 | description="当前CAD图纸的状态", 585 | mimeType="application/json", 586 | ) 587 | ] 588 | 589 | @server.read_resource() 590 | async def handle_read_resource(uri: AnyUrl) -> str: 591 | logger.debug(f"处理 read_resource 请求,URI: {uri}") 592 | if uri.scheme != "drawing": 593 | logger.error(f"不支持的 URI 协议: {uri.scheme}") 594 | raise ValueError(f"不支持的 URI 协议: {uri.scheme}") 595 | 596 | path = str(uri).replace("drawing://", "") 597 | if not path or path != "current": 598 | logger.error(f"未知的资源路径: {path}") 599 | raise ValueError(f"未知的资源路径: {path}") 600 | 601 | return json.dumps(cad_service.drawing_state) 602 | 603 | 604 | @server.list_tools() 605 | async def handle_list_tools() -> list[types.Tool]: 606 | """列出可用工具""" 607 | return [ 608 | types.Tool( 609 | name="draw_line", 610 | description="在CAD中绘制直线", 611 | inputSchema={ 612 | "type": "object", 613 | "properties": { 614 | "start_point": { 615 | "type": "array", 616 | "description": "起点坐标 [x, y, z]", 617 | "items": {"type": "number"}, 618 | "minItems": 2, 619 | "maxItems": 3 620 | }, 621 | "end_point": { 622 | "type": "array", 623 | "description": "终点坐标 [x, y, z]", 624 | "items": {"type": "number"}, 625 | "minItems": 2, 626 | "maxItems": 3 627 | }, 628 | "layer": {"type": "string", "description": "图层名称(可选)"}, 629 | "color": {"type": "string", "description": "颜色名称(可选)"}, 630 | "lineweight": {"type": "number", "description": "线宽(可选)"} 631 | }, 632 | "required": ["start_point", "end_point"], 633 | }, 634 | ), 635 | 636 | types.Tool( 637 | name="draw_circle", 638 | description="在CAD中绘制圆", 639 | inputSchema={ 640 | "type": "object", 641 | "properties": { 642 | "center": { 643 | "type": "array", 644 | "description": "圆心坐标 [x, y, z]", 645 | "items": {"type": "number"}, 646 | "minItems": 2, 647 | "maxItems": 3 648 | }, 649 | "radius": {"type": "number", "description": "圆的半径"}, 650 | "layer": {"type": "string", "description": "图层名称(可选)"}, 651 | "color": {"type": "string", "description": "颜色名称(可选)"}, 652 | "lineweight": {"type": "number", "description": "线宽(可选)"} 653 | }, 654 | "required": ["center", "radius"], 655 | }, 656 | ), 657 | 658 | types.Tool( 659 | name="draw_arc", 660 | description="在CAD中绘制弧", 661 | inputSchema={ 662 | "type": "object", 663 | "properties": { 664 | "center": { 665 | "type": "array", 666 | "description": "圆心坐标 [x, y, z]", 667 | "items": {"type": "number"}, 668 | "minItems": 2, 669 | "maxItems": 3 670 | }, 671 | "radius": {"type": "number", "description": "弧的半径"}, 672 | "start_angle": {"type": "number", "description": "起始角度(度)"}, 673 | "end_angle": {"type": "number", "description": "结束角度(度)"}, 674 | "layer": {"type": "string", "description": "图层名称(可选)"}, 675 | "color": {"type": "string", "description": "颜色名称(可选)"}, 676 | "lineweight": {"type": "number", "description": "线宽(可选)"} 677 | }, 678 | "required": ["center", "radius", "start_angle", "end_angle"], 679 | }, 680 | ), 681 | 682 | types.Tool( 683 | name="draw_ellipse", 684 | description="在CAD中绘制椭圆", 685 | inputSchema={ 686 | "type": "object", 687 | "properties": { 688 | "center": { 689 | "type": "array", 690 | "description": "椭圆中心坐标 [x, y, z]", 691 | "items": {"type": "number"}, 692 | "minItems": 2, 693 | "maxItems": 3 694 | }, 695 | "major_axis": {"type": "number", "description": "长轴长度"}, 696 | "minor_axis": {"type": "number", "description": "短轴长度"}, 697 | "rotation": {"type": "number", "description": "旋转角度(度)(可选)"}, 698 | "layer": {"type": "string", "description": "图层名称(可选)"}, 699 | "color": {"type": "string", "description": "颜色名称(可选)"}, 700 | "lineweight": {"type": "number", "description": "线宽(可选)"} 701 | }, 702 | "required": ["center", "major_axis", "minor_axis"], 703 | }, 704 | ), 705 | 706 | types.Tool( 707 | name="draw_polyline", 708 | description="在CAD中绘制多段线", 709 | inputSchema={ 710 | "type": "object", 711 | "properties": { 712 | "points": { 713 | "type": "array", 714 | "description": "点集 [[x1, y1, z1], [x2, y2, z2], ...]", 715 | "items": { 716 | "type": "array", 717 | "items": {"type": "number"}, 718 | "minItems": 2, 719 | "maxItems": 3 720 | }, 721 | "minItems": 2 722 | }, 723 | "closed": {"type": "boolean", "description": "是否闭合"}, 724 | "layer": {"type": "string", "description": "图层名称(可选)"}, 725 | "color": {"type": "string", "description": "颜色名称(可选)"}, 726 | "lineweight": {"type": "number", "description": "线宽(可选)"} 727 | }, 728 | "required": ["points"], 729 | }, 730 | ), 731 | 732 | types.Tool( 733 | name="draw_rectangle", 734 | description="在CAD中绘制矩形", 735 | inputSchema={ 736 | "type": "object", 737 | "properties": { 738 | "corner1": { 739 | "type": "array", 740 | "description": "第一个角点坐标 [x, y, z]", 741 | "items": {"type": "number"}, 742 | "minItems": 2, 743 | "maxItems": 3 744 | }, 745 | "corner2": { 746 | "type": "array", 747 | "description": "第二个角点坐标 [x, y, z]", 748 | "items": {"type": "number"}, 749 | "minItems": 2, 750 | "maxItems": 3 751 | }, 752 | "layer": {"type": "string", "description": "图层名称(可选)"}, 753 | "color": {"type": "string", "description": "颜色名称(可选)"}, 754 | "lineweight": {"type": "number", "description": "线宽(可选)"} 755 | }, 756 | "required": ["corner1", "corner2"], 757 | }, 758 | ), 759 | 760 | types.Tool( 761 | name="draw_text", 762 | description="在CAD中添加文本", 763 | inputSchema={ 764 | "type": "object", 765 | "properties": { 766 | "position": { 767 | "type": "array", 768 | "description": "插入点坐标 [x, y, z]", 769 | "items": {"type": "number"}, 770 | "minItems": 2, 771 | "maxItems": 3 772 | }, 773 | "text": {"type": "string", "description": "文本内容"}, 774 | "height": {"type": "number", "description": "文本高度"}, 775 | "rotation": {"type": "number", "description": "旋转角度(度)"}, 776 | "layer": {"type": "string", "description": "图层名称(可选)"}, 777 | "color": {"type": "string", "description": "颜色名称(可选)"} 778 | }, 779 | "required": ["position", "text"], 780 | }, 781 | ), 782 | 783 | types.Tool( 784 | name="draw_hatch", 785 | description="在CAD中绘制填充", 786 | inputSchema={ 787 | "type": "object", 788 | "properties": { 789 | "points": { 790 | "type": "array", 791 | "description": "填充边界点集 [[x1, y1, z1], [x2, y2, z2], ...]", 792 | "items": { 793 | "type": "array", 794 | "items": {"type": "number"}, 795 | "minItems": 2, 796 | "maxItems": 3 797 | }, 798 | "minItems": 3 799 | }, 800 | "pattern_name": {"type": "string", "description": "填充图案名称(可选,默认为SOLID)"}, 801 | "scale": {"type": "number", "description": "填充图案比例(可选,默认为1.0)"}, 802 | "layer": {"type": "string", "description": "图层名称(可选)"}, 803 | "color": {"type": "string", "description": "颜色名称(可选)"} 804 | }, 805 | "required": ["points"], 806 | }, 807 | ), 808 | 809 | types.Tool( 810 | name="add_dimension", 811 | description="在CAD中添加线性标注", 812 | inputSchema={ 813 | "type": "object", 814 | "properties": { 815 | "start_point": {"type": "array", "description": "起点坐标 [x, y, z]", "items": {"type": "number"}, "minItems": 2, "maxItems": 3}, 816 | "end_point": {"type": "array", "description": "终点坐标 [x, y, z]", "items": {"type": "number"}, "minItems": 2, "maxItems": 3}, 817 | "text_position": {"type": "array", "description": "文本位置坐标 [x, y, z],可选", "items": {"type": "number"}, "minItems": 2, "maxItems": 3}, 818 | "textheight": {"type": "number", "description": "标注文本高度,可选"}, 819 | "layer": {"type": "string", "description": "图层名称,可选"}, 820 | "color": {"type": "string", "description": "颜色名称,可选"} 821 | }, 822 | "required": ["start_point", "end_point"], 823 | }, 824 | ), 825 | 826 | types.Tool( 827 | name="save_drawing", 828 | description="保存当前图纸", 829 | inputSchema={ 830 | "type": "object", 831 | "properties": { 832 | "file_path": {"type": "string", "description": "保存路径"} 833 | }, 834 | "required": ["file_path"], 835 | }, 836 | ), 837 | 838 | types.Tool( 839 | name="process_command", 840 | description="处理自然语言命令并转换为CAD操作", 841 | inputSchema={ 842 | "type": "object", 843 | "properties": { 844 | "command": {"type": "string", "description": "自然语言命令"} 845 | }, 846 | "required": ["command"], 847 | }, 848 | ), 849 | ] 850 | 851 | @server.call_tool() 852 | async def handle_call_tool( 853 | name: str, arguments: dict[str, Any] | None 854 | ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: 855 | """处理工具执行请求""" 856 | try: 857 | if not arguments: 858 | raise ValueError("缺少参数") 859 | 860 | if name == "draw_line": 861 | start_point = arguments.get("start_point") 862 | end_point = arguments.get("end_point") 863 | layer = arguments.get("layer") 864 | color = arguments.get("color") 865 | lineweight = arguments.get("lineweight") 866 | 867 | # 尝试从命令中提取颜色名称并转换为RGB值 868 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 869 | if color_rgb is not None: 870 | color = color_rgb # 优先使用从命令中提取的颜色 871 | 872 | if not start_point or not end_point: 873 | raise ValueError("缺少起点或终点坐标") 874 | 875 | 876 | result = cad_service.draw_line(start_point, end_point, layer, color, lineweight) 877 | return [types.TextContent(type="text", text=str(result))] 878 | 879 | elif name == "draw_circle": 880 | center = arguments.get("center") 881 | radius = arguments.get("radius") 882 | layer = arguments.get("layer") 883 | color = arguments.get("color") 884 | lineweight = arguments.get("lineweight") 885 | 886 | # 尝试从命令中提取颜色名称并转换为RGB值 887 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 888 | if color_rgb is not None: 889 | color = color_rgb # 优先使用从命令中提取的颜色 890 | 891 | if not center or radius is None: 892 | raise ValueError("缺少圆心坐标或半径") 893 | 894 | 895 | result = cad_service.draw_circle(center, radius, layer, color, lineweight) 896 | return [types.TextContent(type="text", text=str(result))] 897 | 898 | elif name == "draw_arc": 899 | center = arguments.get("center") 900 | radius = arguments.get("radius") 901 | start_angle = arguments.get("start_angle") 902 | end_angle = arguments.get("end_angle") 903 | layer = arguments.get("layer") 904 | color = arguments.get("color") 905 | lineweight = arguments.get("lineweight") 906 | 907 | # 尝试从命令中提取颜色名称并转换为RGB值 908 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 909 | if color_rgb is not None: 910 | color = color_rgb # 优先使用从命令中提取的颜色 911 | 912 | # 详细检查每个参数并提供具体的错误信息 913 | error_msgs = [] 914 | if not center: 915 | error_msgs.append("中心点坐标") 916 | if radius is None: 917 | error_msgs.append("半径") 918 | if start_angle is None: 919 | error_msgs.append("起始角度") 920 | if end_angle is None: 921 | error_msgs.append("结束角度") 922 | 923 | if error_msgs: 924 | raise ValueError(f"缺少必要参数: {', '.join(error_msgs)}") 925 | 926 | result = cad_service.draw_arc(center, radius, start_angle, end_angle, layer, color, lineweight) 927 | return [types.TextContent(type="text", text=str(result))] 928 | 929 | elif name == "draw_ellipse": 930 | center = arguments.get("center") 931 | major_axis = arguments.get("major_axis") 932 | minor_axis = arguments.get("minor_axis") 933 | rotation = arguments.get("rotation") 934 | layer = arguments.get("layer") 935 | color = arguments.get("color") 936 | lineweight = arguments.get("lineweight") 937 | 938 | # 尝试从命令中提取颜色名称并转换为RGB值 939 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 940 | if color_rgb is not None: 941 | color = color_rgb # 优先使用从命令中提取的颜色 942 | 943 | # 详细检查每个参数并提供具体的错误信息 944 | error_msgs = [] 945 | if not center: 946 | error_msgs.append("中心点坐标") 947 | if major_axis is None: 948 | error_msgs.append("长轴") 949 | if minor_axis is None: 950 | error_msgs.append("短轴") 951 | 952 | if error_msgs: 953 | raise ValueError(f"缺少必要参数: {', '.join(error_msgs)}") 954 | 955 | result = cad_service.draw_ellipse(center, major_axis, minor_axis, rotation, layer, color, lineweight) 956 | return [types.TextContent(type="text", text=str(result))] 957 | 958 | elif name == "draw_polyline": 959 | points = arguments.get("points") 960 | closed = arguments.get("closed", False) 961 | layer = arguments.get("layer") 962 | color = arguments.get("color") 963 | lineweight = arguments.get("lineweight") 964 | 965 | # 尝试从命令中提取颜色名称并转换为RGB值 966 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 967 | if color_rgb is not None: 968 | color = color_rgb # 优先使用从命令中提取的颜色 969 | 970 | if not points or len(points) < 2: 971 | raise ValueError("缺少点集或点数不足") 972 | 973 | result = cad_service.draw_polyline(points, closed, layer, color, lineweight) 974 | return [types.TextContent(type="text", text=str(result))] 975 | 976 | elif name == "draw_rectangle": 977 | corner1 = arguments.get("corner1") 978 | corner2 = arguments.get("corner2") 979 | layer = arguments.get("layer") 980 | color = arguments.get("color") 981 | lineweight = arguments.get("lineweight") 982 | 983 | # 尝试从命令中提取颜色名称并转换为RGB值 984 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 985 | if color_rgb is not None: 986 | color = color_rgb # 优先使用从命令中提取的颜色 987 | 988 | if not corner1 or not corner2: 989 | raise ValueError("缺少角点坐标") 990 | 991 | result = cad_service.draw_rectangle(corner1, corner2, layer, color, lineweight) 992 | return [types.TextContent(type="text", text=str(result))] 993 | 994 | 995 | elif name == "draw_text": 996 | position = arguments.get("position") 997 | text = arguments.get("text") 998 | height = arguments.get("height", 2.5) 999 | rotation = arguments.get("rotation", 0) 1000 | layer = arguments.get("layer") 1001 | color = arguments.get("color") 1002 | 1003 | # 尝试从命令中提取颜色名称并转换为RGB值 1004 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 1005 | if color_rgb is not None: 1006 | color = color_rgb # 优先使用从命令中提取的颜色 1007 | 1008 | if not position or not text: 1009 | raise ValueError("缺少插入点坐标或文本内容") 1010 | 1011 | result = cad_service.draw_text(position, text, height, rotation, layer, color) 1012 | return [types.TextContent(type="text", text=str(result))] 1013 | 1014 | 1015 | elif name == "draw_hatch": 1016 | points = arguments.get("points") 1017 | pattern_name = arguments.get("pattern_name", "SOLID") 1018 | scale = arguments.get("scale", 1.0) 1019 | layer = arguments.get("layer") 1020 | color = arguments.get("color") 1021 | 1022 | # 尝试从命令中提取颜色名称并转换为RGB值 1023 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 1024 | if color_rgb is not None: 1025 | color = color_rgb # 优先使用从命令中提取的颜色 1026 | 1027 | if not points or len(points) < 3: 1028 | raise ValueError("缺少点集或点数不足,填充边界至少需要3个点") 1029 | 1030 | result = cad_service.draw_hatch(points, pattern_name, scale, layer, color) 1031 | return [types.TextContent(type="text", text=str(result))] 1032 | 1033 | elif name == "save_drawing": 1034 | file_path = arguments.get("file_path") 1035 | 1036 | if not file_path: 1037 | raise ValueError("缺少保存路径") 1038 | 1039 | result = cad_service.save_drawing(file_path) 1040 | return [types.TextContent(type="text", text=str(result))] 1041 | 1042 | elif name == "add_dimension": 1043 | start_point = arguments.get("start_point") 1044 | end_point = arguments.get("end_point") 1045 | text_position = arguments.get("text_position") 1046 | layer = arguments.get("layer") 1047 | color = arguments.get("color") 1048 | textheight = arguments.get("textheight") 1049 | 1050 | # 尝试从命令中提取颜色名称并转换为RGB值 1051 | color_rgb = cad_service.nlp_processor.extract_color_from_command(color) 1052 | if color_rgb is not None: 1053 | color = color_rgb # 优先使用从命令中提取的颜色 1054 | 1055 | # 详细检查每个参数并提供具体的错误信息 1056 | error_msgs = [] 1057 | if not start_point: 1058 | error_msgs.append("起点坐标") 1059 | if not end_point: 1060 | error_msgs.append("终点坐标") 1061 | 1062 | if error_msgs: 1063 | raise ValueError(f"缺少必要参数: {', '.join(error_msgs)}") 1064 | 1065 | result = cad_service.add_dimension(start_point, end_point, text_position, textheight, layer, color) 1066 | return [types.TextContent(type="text", text=str(result))] 1067 | 1068 | elif name == "process_command": 1069 | command = arguments.get("command") 1070 | 1071 | if not command: 1072 | raise ValueError("缺少命令") 1073 | 1074 | result = cad_service.process_command(command) 1075 | return [types.TextContent(type="text", text=str(result))] 1076 | else: 1077 | raise ValueError(f"未知工具: {name}") 1078 | 1079 | except Exception as e: 1080 | return [types.TextContent(type="text", text=f"错误: {str(e)}")] 1081 | 1082 | @server.list_prompts() 1083 | async def handle_list_prompts() -> list[types.Prompt]: 1084 | logger.debug("处理 list_prompts 请求") 1085 | return [ 1086 | types.Prompt( 1087 | name="cad-assistant", 1088 | description="一个用于通过自然语言控制CAD的助手", 1089 | arguments=[], 1090 | ) 1091 | ] 1092 | 1093 | @server.get_prompt() 1094 | async def handle_get_prompt(name: str, arguments: dict[str, str] | None) -> types.GetPromptResult: 1095 | logger.debug(f"处理 get_prompt 请求,名称: {name},参数: {arguments}") 1096 | if name != "cad-assistant": 1097 | logger.error(f"未知的提示: {name}") 1098 | raise ValueError(f"未知的提示: {name}") 1099 | 1100 | prompt = PROMPT_TEMPLATE 1101 | logger.debug("生成提示模板") 1102 | 1103 | return types.GetPromptResult( 1104 | description="CAD助手提示模板", 1105 | messages=[ 1106 | types.PromptMessage( 1107 | role="user", 1108 | content=types.TextContent(type="text", text=prompt.strip()), 1109 | ) 1110 | ], 1111 | ) 1112 | 1113 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): 1114 | logger.info("服务器正在使用 stdio 传输运行") 1115 | await server.run( 1116 | read_stream, 1117 | write_stream, 1118 | InitializationOptions( 1119 | server_name=config.server_name, 1120 | server_version=config.server_version, 1121 | capabilities=server.get_capabilities( 1122 | notification_options=NotificationOptions(), 1123 | experimental_capabilities={}, 1124 | ), 1125 | ), 1126 | ) 1127 | 1128 | if __name__ == "__main__": 1129 | import asyncio 1130 | asyncio.run(main()) --------------------------------------------------------------------------------