FreeCAD MCP

import os import FreeCAD as App import FreeCADGui as Gui import json import socket import threading import time import traceback from PySide import QtCore, QtGui class FreeCADMCPServer: def __init__(self, host='localhost', port=9876): self.host = host self.port = port self.running = False self.socket = None self.client = None self.buffer = b'' self.timer = None def start(self): self.running = True self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: self.socket.bind((self.host, self.port)) self.socket.listen(1) self.socket.setblocking(False) self.timer = QtCore.QTimer() self.timer.timeout.connect(self._process_server) self.timer.start(100) # 100ms interval App.Console.PrintMessage(f"FreeCAD MCP server started on {self.host}:{self.port}\n") except Exception as e: App.Console.PrintError(f"Failed to start server: {str(e)}\n") self.stop() def stop(self): self.running = False if self.timer: self.timer.stop() self.timer = None if self.socket: self.socket.close() if self.client: self.client.close() self.socket = None self.client = None App.Console.PrintMessage("FreeCAD MCP server stopped\n") def _process_server(self): if not self.running: return try: if not self.client and self.socket: try: self.client, address = self.socket.accept() self.client.setblocking(False) App.Console.PrintMessage(f"Connected to client: {address}\n") except BlockingIOError: pass except Exception as e: App.Console.PrintError(f"Error accepting connection: {str(e)}\n") if self.client: try: try: data = self.client.recv(8192) if data: self.buffer += data try: command = json.loads(self.buffer.decode('utf-8')) self.buffer = b'' response = self.execute_command(command) response_json = json.dumps(response) self.client.sendall(response_json.encode('utf-8')) except json.JSONDecodeError: pass else: App.Console.PrintMessage("Client disconnected\n") self.client.close() self.client = None self.buffer = b'' except BlockingIOError: pass except Exception as e: App.Console.PrintError(f"Error receiving data: {str(e)}\n") self.client.close() self.client = None self.buffer = b'' except Exception as e: App.Console.PrintError(f"Error with client: {str(e)}\n") if self.client: self.client.close() self.client = None self.buffer = b'' except Exception as e: App.Console.PrintError(f"Server error: {str(e)}\n") def execute_command(self, command): try: cmd_type = command.get("type") params = command.get("params", {}) handlers = { "send_command": self.handle_send_command, "run_script": self.handle_run_script } handler = handlers.get(cmd_type) if handler: try: App.Console.PrintMessage(f"Executing handler for {cmd_type}\n") result = handler(**params) return {"status": "success", "result": result} except Exception as e: App.Console.PrintError(f"Error in handler: {str(e)}\n") traceback.print_exc() return {"status": "error", "message": str(e)} else: return {"status": "error", "message": f"Unknown command type: {cmd_type}"} except Exception as e: App.Console.PrintError(f"Error executing command: {str(e)}\n") traceback.print_exc() return {"status": "error", "message": str(e)} def handle_send_command(self, command, get_context=True): """Handle a send_command request with document context""" try: # Execute the command exec(command, {"App": App, "Gui": Gui}) # Get document context if requested context = {} if get_context: context = self.get_document_context() return { "command_result": "success", "context": context } except Exception as e: return { "command_result": "error", "error": str(e), "traceback": traceback.format_exc() } def handle_run_script(self, script): """Handle a run_script request""" try: # Create a new local namespace for the script namespace = { "App": App, "Gui": Gui, "doc": App.ActiveDocument } # Execute the script exec(script, namespace) return { "script_result": "success" } except Exception as e: return { "script_result": "error", "error": str(e), "traceback": traceback.format_exc() } def get_document_context(self): """Get comprehensive information about the current document state""" doc = App.ActiveDocument if not doc: return { "document": None, "objects": [], "view": None } # Document info doc_info = { "name": doc.Name, "filename": doc.FileName if hasattr(doc, "FileName") else None, "object_count": len(doc.Objects) } # Objects info objects = [] for obj in doc.Objects: obj_info = { "name": obj.Name, "label": obj.Label, "type": obj.TypeId, "visibility": obj.ViewObject.Visibility if hasattr(obj, "ViewObject") else None } # Add placement if available if hasattr(obj, "Placement"): pos = obj.Placement.Base rot = obj.Placement.Rotation obj_info["placement"] = { "position": [float(pos.x), float(pos.y), float(pos.z)], "rotation": [float(rot.Axis.x), float(rot.Axis.y), float(rot.Axis.z), float(rot.Angle)] } # Add shape properties if available if hasattr(obj, "Shape"): shape = obj.Shape obj_info["shape"] = { "type": shape.ShapeType, "volume": float(shape.Volume) if hasattr(shape, "Volume") else None, "area": float(shape.Area) if hasattr(shape, "Area") else None } objects.append(obj_info) # View state view_info = None if Gui.ActiveDocument: cam = Gui.ActiveDocument.ActiveView.getCameraNode() view_info = { "camera_type": cam.getTypeId(), "camera_position": [float(x) for x in cam.position.getValue()], "camera_orientation": [float(x) for x in cam.orientation.getValue()] } return { "document": doc_info, "objects": objects, "view": view_info } class FreeCADMCPPanel: def __init__(self): self.form = QtGui.QWidget() self.form.setWindowTitle("FreeCAD MCP") layout = QtGui.QVBoxLayout(self.form) # Server status self.status_label = QtGui.QLabel("Server: Stopped") layout.addWidget(self.status_label) # Start/Stop buttons button_layout = QtGui.QHBoxLayout() self.start_button = QtGui.QPushButton("Start Server") self.stop_button = QtGui.QPushButton("Stop Server") self.stop_button.setEnabled(False) self.start_button.clicked.connect(self.start_server) self.stop_button.clicked.connect(self.stop_server) button_layout.addWidget(self.start_button) button_layout.addWidget(self.stop_button) layout.addLayout(button_layout) # Server instance self.server = None def start_server(self): if not self.server: self.server = FreeCADMCPServer() self.server.start() self.status_label.setText("Server: Running") self.start_button.setEnabled(False) self.stop_button.setEnabled(True) def stop_server(self): if self.server: self.server.stop() self.server = None self.status_label.setText("Server: Stopped") self.start_button.setEnabled(True) self.stop_button.setEnabled(False) def show_panel(): panel = FreeCADMCPPanel() Gui.Control.showDialog(panel)