Skip to main content
Glama

CAD-MCP Server

import logging import math import time import os import json from typing import Any, Dict, List, Optional, Tuple, Union # 直接读取config.json文件 config_path = os.path.join(os.path.dirname(__file__), 'config.json') with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) try: import win32com.client # pythoncom是pywin32的一部分,不需要单独安装 import pythoncom except ImportError: logging.error("无法导入win32com.client或pythoncom,请确保已安装pywin32库") raise logger = logging.getLogger('cad_controller') class CADController: """CAD控制器类,负责与CAD应用程序交互""" def __init__(self): """初始化CAD控制器""" self.app = None self.doc = None self.entities = {} # 存储已创建图形的实体引用,用于后续修改 # 从配置文件加载参数 self.startup_wait_time = config["cad"]["startup_wait_time"] self.command_delay = config["cad"]["command_delay"] # 获取CAD类型 self.cad_type = config["cad"]["type"] # 有效的线宽值列表 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] logger.info("CAD控制器已初始化") def start_cad(self) -> bool: """启动CAD并创建或打开一个文档""" try: # 初始化COM pythoncom.CoInitialize() # 存储旧实例引用(如果有)以便后续清理 old_app = None if self.app is not None: old_app = self.app self.app = None self.doc = None try: # 根据配置的CAD类型选择不同的应用程序标识符 app_id = "AutoCAD.Application" app_name = "AutoCAD" if self.cad_type.lower() == "autocad": app_id = "AutoCAD.Application" app_name = "AutoCAD" elif self.cad_type.lower() == "gcad": app_id = "GCAD.Application" app_name = "浩辰CAD" elif self.cad_type.lower() == "gstarcad": app_id = "GCAD.Application" app_name = "浩辰CAD" elif self.cad_type.lower() == "zwcad": app_id = "ZWCAD.Application" app_name = "中望CAD" # 尝试连接到已运行的CAD实例 logger.info(f"尝试连接现有{app_name}实例...") try: self.app = win32com.client.GetActiveObject(app_id) logger.info(f"成功连接到已运行的{app_name}实例") except Exception as e: logger.info(f"未找到运行中的{app_name}实例,将尝试启动新实例: {str(e)}") raise # 已在上面的代码中处理 # 如果当前没有文档,创建一个新文档 try: if self.app.Documents.Count == 0: logger.info("创建新文档...") self.doc = self.app.Documents.Add() else: logger.info("获取活动文档...") self.doc = self.app.ActiveDocument except Exception as doc_ex: # 如果获取文档失败,强制创建新文档 logger.warning(f"获取文档失败,尝试创建新文档: {str(doc_ex)}") try: # 关闭所有打开的文档 for i in range(self.app.Documents.Count): try: self.app.Documents.Item(0).Close(False) # 不保存 except: pass # 创建新文档 self.doc = self.app.Documents.Add() except Exception as new_doc_ex: logger.error(f"创建新文档失败: {str(new_doc_ex)}") raise except Exception as app_ex: # 如果连接失败,启动一个新实例 logger.info(f"连接失败,正在启动新的CAD实例: {str(app_ex)}") try: # 根据配置的CAD类型启动相应的应用程序 app_id = "AutoCAD.Application" app_name = "AutoCAD" if self.cad_type.lower() == "autocad": app_id = "AutoCAD.Application" app_name = "AutoCAD" elif self.cad_type.lower() == "gcad": app_id = "GCAD.Application" app_name = "浩辰CAD" elif self.cad_type.lower() == "gstarcad": app_id = "GCAD.Application" app_name = "浩辰CAD" elif self.cad_type.lower() == "zwcad": app_id = "ZWCAD.Application" app_name = "中望CAD" logger.info(f"正在启动{app_name}实例...") self.app = win32com.client.Dispatch(app_id) self.app.Visible = True # 等待CAD启动 time.sleep(self.startup_wait_time) # 使用配置的等待时间 # 创建新文档 logger.info("尝试创建新文档...") # self.doc = self.app.Documents.Add() self.doc = self.app.ActiveDocument except Exception as new_app_ex: logger.error(f"启动新CAD实例失败: {str(new_app_ex)}") raise # 额外安全检查和等待 time.sleep(2) # 给CAD更多时间处理文档创建 if self.doc is None: raise Exception("无法获取有效的Document对象") # 尝试读取文档属性以验证其有效性 try: name = self.doc.Name logger.info(f"文档名称: {name}") except Exception as name_ex: logger.error(f"无法读取文档名称: {str(name_ex)}") raise Exception("文档对象无效") logger.info("CAD已成功启动和准备") return True except Exception as e: logger.error(f"启动CAD失败: {str(e)}") return False finally: # 清理旧实例 if old_app is not None: try: del old_app except: pass def is_running(self) -> bool: """检查CAD是否正在运行""" return self.app is not None and self.doc is not None def save_drawing(self, file_path: str) -> bool: """保存当前图纸到指定路径""" if not self.is_running(): logger.error("CAD未运行,无法保存图纸") return False try: # 确保目录存在 os.makedirs(os.path.dirname(file_path), exist_ok=True) # 保存文件 self.doc.SaveAs(file_path) logger.info(f"图纸已保存到: {file_path}") return True except Exception as e: logger.error(f"保存图纸失败: {str(e)}") return False def refresh_view(self) -> None: """刷新CAD视图""" if self.is_running(): try: self.doc.Regen(1) # acAllViewports = 1 except Exception as e: logger.error(f"刷新视图失败: {str(e)}") def validate_lineweight(self, lineweight) -> int: """验证并返回有效的线宽值 如果提供的线宽值不在有效值列表中,则返回默认值0 Args: lineweight: 要验证的线宽值 Returns: 有效的线宽值 """ if lineweight is None: return None # 检查线宽是否在有效值列表中 if lineweight in self.valid_lineweights: return lineweight else: logger.warning(f"线宽值 {lineweight} 无效,将使用默认值 0") return 0 def draw_line(self, start_point: Tuple[float, float, float], end_point: Tuple[float, float, float], layer: str = None, color: int = None, lineweight=None) -> bool: """绘制直线""" if not self.is_running(): return False try: # 确保点是三维的 if len(start_point) == 2: start_point = (start_point[0], start_point[1], 0) if len(end_point) == 2: end_point = (end_point[0], end_point[1], 0) # 使用VARIANT包装坐标点数据 start_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [start_point[0], start_point[1], start_point[2]]) end_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [end_point[0], end_point[1], end_point[2]]) # 添加直线 line = self.doc.ModelSpace.AddLine(start_array, end_array) # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 line.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: line.Color = color if lineweight is not None: line.LineWeight = self.validate_lineweight(lineweight) # 刷新视图 self.refresh_view() logger.debug(f"已绘制直线: 起点{start_point}, 终点{end_point}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return line except Exception as e: logger.error(f"绘制直线时出错: {str(e)}") return None def draw_circle(self, center: Tuple[float, float, float], radius: float, layer: str = None, color: int = None, lineweight=None) -> Any: """绘制圆""" if not self.is_running(): return None try: # 确保点是三维的 if len(center) == 2: center = (center[0], center[1], 0) # 使用VARIANT包装坐标点数据 center_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [center[0], center[1], center[2]]) # 添加圆 circle = self.doc.ModelSpace.AddCircle(center_array, radius) # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 circle.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: circle.Color = color if lineweight is not None: circle.LineWeight = self.validate_lineweight(lineweight) # 刷新视图 self.refresh_view() logger.debug(f"已绘制圆: 中心{center}, 半径{radius}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return circle except Exception as e: logger.error(f"绘制圆时出错: {str(e)}") return None def draw_arc(self, center: Tuple[float, float, float], radius: float, start_angle: float, end_angle: float, layer: str = None, color: int = None, lineweight=None) -> Any: """绘制圆弧""" if not self.is_running(): return None try: # 确保点是三维的 if len(center) == 2: center = (center[0], center[1], 0) # 将角度转换为弧度 start_rad = math.radians(start_angle) end_rad = math.radians(end_angle) # 使用VARIANT包装坐标点数据 center_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [center[0], center[1], center[2]]) # 添加圆弧 arc = self.doc.ModelSpace.AddArc(center_array, radius, start_rad, end_rad) # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 arc.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: arc.Color = color if lineweight is not None: arc.LineWeight = self.validate_lineweight(lineweight) # 刷新视图 self.refresh_view() logger.debug(f"已绘制圆弧: 中心{center}, 半径{radius}, 起始角度{start_angle}, 结束角度{end_angle}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return arc except Exception as e: logger.error(f"绘制圆弧失败: {str(e)}") return None def draw_ellipse(self, center: Tuple[float, float, float], major_axis: float, minor_axis: float, rotation: float = 0, layer: str = None, color: int = None, lineweight=None) -> Any: """绘制椭圆""" if not self.is_running(): return None try: # 确保点是三维的 if len(center) == 2: center = (center[0], center[1], 0) if rotation is None: rotation = 0 # 将旋转角度转换为弧度 rotation_rad = math.radians(rotation) # 使用VARIANT包装坐标点数据 center_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [center[0], center[1], center[2]]) # 计算椭圆的主轴向量 major_x = major_axis * math.cos(rotation_rad) major_y = major_axis * math.sin(rotation_rad) major_vector = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [major_x, major_y, 0]) # 添加椭圆 ellipse = self.doc.ModelSpace.AddEllipse(center_array, major_vector, minor_axis / major_axis) # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 ellipse.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: ellipse.Color = color if lineweight is not None: ellipse.LineWeight = self.validate_lineweight(lineweight) # 刷新视图 self.refresh_view() logger.debug(f"已绘制椭圆: 中心{center}, 长轴{major_axis}, 短轴{minor_axis}, 旋转角度{rotation}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return ellipse except Exception as e: logger.error(f"绘制椭圆失败: {str(e)}") return None def draw_polyline(self, points: List[Tuple[float, float, float]], closed: bool = False, layer: str = None, color: int = None, lineweight=None) -> Any: """绘制多段线""" if not self.is_running(): return None try: # 确保所有点都是三维的 processed_points = [] for point in points: if len(point) == 2: processed_points.append((point[0], point[1], 0)) else: processed_points.append(point) # 创建点数组 point_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [coord for point in processed_points for coord in point]) # 添加多段线 polyline = self.doc.ModelSpace.AddPolyline(point_array) # 如果需要闭合 if closed and len(processed_points) > 2: polyline.Closed = True # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 polyline.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: polyline.Color = color if lineweight is not None: polyline.LineWeight = self.validate_lineweight(lineweight) # 刷新视图 self.refresh_view() logger.debug(f"已绘制多段线: {len(points)}个点, {'闭合' if closed else '不闭合'}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return polyline except Exception as e: logger.error(f"绘制多段线时出错: {str(e)}") return None def draw_rectangle(self, corner1: Tuple[float, float, float], corner2: Tuple[float, float, float], layer: str = None, color: int = None, lineweight=None) -> Any: """绘制矩形""" if not self.is_running(): return None try: # 确保点是三维的 if len(corner1) == 2: corner1 = (corner1[0], corner1[1], 0) if len(corner2) == 2: corner2 = (corner2[0], corner2[1], 0) # 计算矩形的四个角点 x1, y1, z1 = corner1 x2, y2, z2 = corner2 # 创建矩形的四个点 points = [ (x1, y1, z1), (x2, y1, z1), (x2, y2, z1), (x1, y2, z1), (x1, y1, z1) # 闭合矩形 ] # 使用多段线绘制矩形 return self.draw_polyline(points, True, layer, color, lineweight) except Exception as e: logger.error(f"绘制矩形时出错: {str(e)}") return None def draw_text(self, position: Tuple[float, float, float], text: str, height: float = 2.5, rotation: float = 0, layer: str = None, color: int = None) -> Any: """添加文本""" if not self.is_running(): return None try: # 确保点是三维的 if len(position) == 2: position = (position[0], position[1], 0) # 使用VARIANT包装坐标点数据 position_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [position[0], position[1], position[2]]) # 添加文本 text_obj = self.doc.ModelSpace.AddText(text, position_array, height) # 设置旋转角度 if rotation != 0: text_obj.Rotation = math.radians(rotation) # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 text_obj.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: text_obj.Color = color # 刷新视图 self.refresh_view() logger.debug(f"已添加文本: '{text}', 位置{position}, 高度{height}, 旋转{rotation}度, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return text_obj except Exception as e: logger.error(f"添加文本时出错: {str(e)}") return None def draw_hatch(self, points: List[Tuple[float, float, float]], pattern_name: str = "SOLID", scale: float = 1.0, layer: str = None, color: int = None) -> Any: """绘制填充图案 Args: points: 填充边界的点集,每个点为二维或三维坐标元组 pattern_name: 填充图案名称,默认为"SOLID"(实体填充) scale: 填充图案比例,默认为1.0 layer: 图层名称,如果为None则使用当前图层 color: 颜色索引,如果为None则使用默认颜色 Returns: 成功返回填充对象,失败返回None """ if not self.is_running(): return None try: # 确保所有点都是有效的 if not points or len(points) < 3: logger.error("创建填充失败: 至少需要3个点来定义填充边界") return None # 创建闭合多段线作为边界 closed_polyline = self.draw_polyline(points, closed=True, layer=layer) if not closed_polyline: logger.error("创建填充失败: 无法创建边界多段线") return None # 创建填充对象 (0表示正常填充,True表示关联边界) hatch = self.doc.ModelSpace.AddHatch(0, pattern_name, True) # 添加外部边界循环 # 使用VARIANT包装对象数组 object_ids = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_DISPATCH, [closed_polyline]) hatch.AppendOuterLoop(object_ids) # 设置填充图案比例 hatch.PatternScale = scale # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 hatch.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: hatch.Color = color # 更新填充 (计算填充区域) hatch.Evaluate() # 刷新视图 self.refresh_view() logger.debug(f"已创建填充: 图案 {pattern_name}, 比例 {scale}, 图层{layer if layer else '默认'}, 颜色{color if color is not None else '默认'}") return hatch except Exception as e: logger.error(f"创建填充时出错: {str(e)}") return None def zoom_extents(self) -> bool: """缩放视图以显示所有对象""" if not self.is_running(): return False try: self.doc.ActiveViewport.ZoomExtents() logger.info("已缩放视图以显示所有对象") return True except Exception as e: logger.error(f"缩放视图时出错: {str(e)}") return False def close(self) -> None: """关闭CAD控制器""" try: # 释放COM资源 if self.app is not None: del self.app pythoncom.CoUninitialize() except: pass def create_layer(self, layer_name: str) -> bool: # , color: Union[int, Tuple[int, int, int]] = 7 """创建新图层 Args: layer_name: 图层名称 color: 颜色值,可以是CAD颜色索引(int)或RGB颜色值(tuple) Returns: 操作是否成功 """ if not self.is_running(): return False try: # 检查图层是否已存在 for i in range(self.doc.Layers.Count): if self.doc.Layers.Item(i).Name == layer_name: # 图层已存在,激活它 self.doc.ActiveLayer = self.doc.Layers.Item(i) return True # 创建新图层 new_layer = self.doc.Layers.Add(layer_name) # 图层不设置颜色,设置里面的实体颜色 # # 设置颜色 # if isinstance(color, int): # # 使用颜色索引 # new_layer.Color = color # elif isinstance(color, tuple) and len(color) == 3: # # 使用RGB值 # r, g, b = color # # 设置TrueColor # new_layer.TrueColor = self._create_true_color(r, g, b) # 设置为当前图层 self.doc.ActiveLayer = new_layer logger.info(f"已创建新图层: {layer_name}") #, 颜色: {color} return True except Exception as e: logger.error(f"创建图层时出错: {str(e)}") return False def add_dimension(self, start_point: Tuple[float, float, float], end_point: Tuple[float, float, float], text_position: Tuple[float, float, float] = None, textheight: float = 5,layer: str = None, color: int=None) -> Any: """添加线性标注""" if not self.is_running(): return None try: # 确保点是三维的 if len(start_point) == 2: start_point = (start_point[0], start_point[1], 0) if len(end_point) == 2: end_point = (end_point[0], end_point[1], 0) # 如果未提供文本位置,自动计算 if text_position is None: # 在起点和终点之间的中点上方 mid_x = (start_point[0] + end_point[0]) / 2 mid_y = (start_point[1] + end_point[1]) / 2 text_position = (mid_x, mid_y + 5, 0) elif len(text_position) == 2: text_position = (text_position[0], text_position[1], 0) # 使用VARIANT包装坐标点数据 start_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [start_point[0], start_point[1], start_point[2]]) end_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [end_point[0], end_point[1], end_point[2]]) text_pos_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [text_position[0], text_position[1], text_position[2]]) # 添加对齐标注 dimension = self.doc.ModelSpace.AddDimAligned(start_array, end_array, text_pos_array) # 设置文字高度 if textheight is not None: dimension.TextHeight = textheight # 如果指定了图层,设置图层 if layer: # 确保图层存在 self.create_layer(layer) # 设置实体的图层 dimension.Layer = layer # 如果指定了颜色,设置颜色 if color is not None: dimension.Color = color # 刷新视图 self.refresh_view() logger.info(f"已添加标注: 从 {start_point} 到 {end_point}, 图层{layer if layer else '默认'}") return dimension except Exception as e: logger.error(f"添加标注时出错: {str(e)}") return None

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/daobataotie/CAD-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server