Skip to main content
Glama
freecad_ai_workbench.py39.8 kB
"""FreeCAD AI Workbench - Main workbench implementation""" import functools import os import sys import traceback import FreeCAD import FreeCADGui # Ensure the addon directory is in the Python path addon_dir = os.path.dirname(os.path.abspath(__file__)) if addon_dir not in sys.path: sys.path.insert(0, addon_dir) def workbench_crash_safe(operation_name): """Decorator to wrap workbench methods with comprehensive crash prevention and logging.""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: FreeCAD.Console.PrintMessage( f"FreeCAD AI Workbench: Starting {operation_name}...\n" ) result = func(*args, **kwargs) FreeCAD.Console.PrintMessage( f"FreeCAD AI Workbench: {operation_name} completed successfully\n" ) return result except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI Workbench: CRASH PREVENTED in {operation_name}: {e}\n" ) FreeCAD.Console.PrintError( f"FreeCAD AI Workbench: {operation_name} traceback: {traceback.format_exc()}\n" ) # Try to show error in GUI if possible try: if hasattr(FreeCADGui, "getMainWindow"): main_window = FreeCADGui.getMainWindow() if main_window and hasattr(main_window, "statusBar"): main_window.statusBar().showMessage( f"FreeCAD AI: Error in {operation_name}", 5000 ) except (AttributeError, RuntimeError): # AttributeError: Missing statusBar or showMessage method # RuntimeError: GUI object invalid or destroyed pass # Return None or appropriate fallback return None return wrapper return decorator def safe_import_with_fallback(import_func, module_name, fallback_value=None): """Safely import modules with comprehensive error handling and fallback.""" try: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Attempting to import {module_name}...\n" ) result = import_func() FreeCAD.Console.PrintMessage( f"FreeCAD AI: Successfully imported {module_name}\n" ) return result except ImportError as e: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Import failed for {module_name}: {e}\n" ) return fallback_value except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: CRASH PREVENTED during {module_name} import: {e}\n" ) FreeCAD.Console.PrintError( f"FreeCAD AI: {module_name} import traceback: {traceback.format_exc()}\n" ) return fallback_value def safe_gui_operation(operation_func, operation_name, fallback_result=None): """Safely execute GUI operations with comprehensive error handling.""" try: FreeCAD.Console.PrintMessage(f"FreeCAD AI: Attempting {operation_name}...\n") result = operation_func() FreeCAD.Console.PrintMessage(f"FreeCAD AI: {operation_name} successful\n") return result except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: CRASH PREVENTED in {operation_name}: {e}\n" ) FreeCAD.Console.PrintError( f"FreeCAD AI: {operation_name} traceback: {traceback.format_exc()}\n" ) return fallback_result # Try to import PySide2, fall back gracefully if not available try: from PySide2 import QtCore, QtWidgets HAS_PYSIDE2 = True except ImportError: try: from PySide import QtCore from PySide import QtGui as QtWidgets HAS_PYSIDE2 = False FreeCAD.Console.PrintWarning("FreeCAD AI: Using PySide instead of PySide2\n") except ImportError: FreeCAD.Console.PrintError("FreeCAD AI: No Qt bindings available\n") HAS_PYSIDE2 = False # Import tools with simplified strategy TOOLS_AVAILABLE = False TOOLS_IMPORT_ERROR = None def import_tools_safely(): """Import tools using a simplified, predictable strategy.""" global TOOLS_AVAILABLE, TOOLS_IMPORT_ERROR # Ensure addon directory is in path if addon_dir not in sys.path: sys.path.insert(0, addon_dir) try: # Try direct import with clear error handling from tools import TOOLS_AVAILABLE as TOOLS_LOADED from tools import ( ExportImportTool, MeasurementsTool, OperationsTool, PrimitivesTool, ) TOOLS_AVAILABLE = TOOLS_LOADED FreeCAD.Console.PrintMessage("FreeCAD AI: Tools imported successfully\n") return True except ImportError as e: TOOLS_IMPORT_ERROR = str(e) FreeCAD.Console.PrintMessage(f"FreeCAD AI: Tools import failed: {e}\n") # Create minimal fallback tools for graceful degradation global PrimitivesTool, OperationsTool, MeasurementsTool, ExportImportTool class FallbackTool: def __init__(self, name): self.name = name def __str__(self): return f"Fallback{self.name}" PrimitivesTool = FallbackTool("PrimitivesTool") OperationsTool = FallbackTool("OperationsTool") MeasurementsTool = FallbackTool("MeasurementsTool") ExportImportTool = FallbackTool("ExportImportTool") TOOLS_AVAILABLE = True # Partial availability with fallbacks FreeCAD.Console.PrintMessage("FreeCAD AI: Tools using fallback implementations\n") return False except Exception as e: TOOLS_IMPORT_ERROR = str(e) FreeCAD.Console.PrintError(f"FreeCAD AI: Critical tools import error: {e}\n") TOOLS_AVAILABLE = False return False # Execute the import import_tools_safely() if not TOOLS_AVAILABLE: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Tools not available - last error: {TOOLS_IMPORT_ERROR}\n" ) # Import advanced tools with graceful degradation ADVANCED_TOOLS_AVAILABLE = safe_import_with_fallback( lambda: __import__( "tools.advanced", fromlist=["ADVANCED_TOOLS_AVAILABLE"] ).ADVANCED_TOOLS_AVAILABLE, "advanced tools", False, ) # Import resources with graceful degradation RESOURCES_AVAILABLE = safe_import_with_fallback( lambda: __import__( "resources", fromlist=["RESOURCES_AVAILABLE"] ).RESOURCES_AVAILABLE, "resources", False, ) # Import events with graceful degradation EVENTS_AVAILABLE = safe_import_with_fallback( lambda: __import__("events", fromlist=["EVENTS_AVAILABLE"]).EVENTS_AVAILABLE, "events", False, ) # Import API with simplified compatibility handling API_AVAILABLE = False API_IMPORT_ERROR = None def import_api_safely(): """Import API with simplified compatibility checking.""" global API_AVAILABLE, API_IMPORT_ERROR try: # Quick compatibility check for Python 3.13+ python_version = sys.version_info if python_version >= (3, 13): # Test basic FastAPI/Pydantic functionality try: import fastapi from pydantic import BaseModel # Simple test class TestModel(BaseModel): test_field: str = "test" FreeCAD.Console.PrintMessage("FreeCAD AI: API compatibility verified\n") except (TypeError, ImportError) as e: if "Protocols with non-method members" in str(e): # This is a known compatibility warning with Python 3.13+ that doesn't prevent functionality API_IMPORT_ERROR = f"Python 3.13+ compatibility warning (non-blocking): {e}" FreeCAD.Console.PrintWarning(f"FreeCAD AI: API compatibility warning - {API_IMPORT_ERROR}\n") FreeCAD.Console.PrintMessage("FreeCAD AI: Continuing with API load despite warning...\n") # Don't return False here - continue with API loading else: # Continue trying despite minor issues FreeCAD.Console.PrintMessage(f"FreeCAD AI: API compatibility warning: {e}\n") # Import the API module from api import API_AVAILABLE as API_LOADED API_AVAILABLE = API_LOADED if API_AVAILABLE: FreeCAD.Console.PrintMessage("FreeCAD AI: API loaded successfully\n") else: FreeCAD.Console.PrintMessage("FreeCAD AI: API module reports unavailable\n") return API_AVAILABLE except (ImportError, TypeError, AttributeError) as e: API_IMPORT_ERROR = str(e) FreeCAD.Console.PrintMessage(f"FreeCAD AI: API not available: {e}\n") API_AVAILABLE = False return False except Exception as e: API_IMPORT_ERROR = str(e) FreeCAD.Console.PrintError(f"FreeCAD AI: Unexpected API import error: {e}\n") API_AVAILABLE = False return False # Execute API import import_api_safely() # Import clients with graceful degradation CLIENTS_AVAILABLE = safe_import_with_fallback( lambda: __import__("clients", fromlist=["CLIENTS_AVAILABLE"]).CLIENTS_AVAILABLE, "clients", False, ) # Import AI providers with comprehensive fallback strategies and dependency management AI_PROVIDERS_AVAILABLE = False AI_PROVIDERS_IMPORT_ERROR = None DEPENDENCY_INSTALL_ATTEMPTED = False def attempt_dependency_installation(): """Attempt to install missing dependencies for AI providers.""" global DEPENDENCY_INSTALL_ATTEMPTED if DEPENDENCY_INSTALL_ATTEMPTED: return False DEPENDENCY_INSTALL_ATTEMPTED = True try: FreeCAD.Console.PrintMessage( "FreeCAD AI: Attempting automatic dependency installation for AI providers...\n" ) # Try to use the dependency manager from utils.dependency_manager import DependencyManager def progress_callback(message): FreeCAD.Console.PrintMessage(f"FreeCAD AI: {message}\n") manager = DependencyManager(progress_callback) # Install critical dependencies (like aiohttp) success = manager.install_missing_dependencies(critical_only=True) if success: FreeCAD.Console.PrintMessage( "FreeCAD AI: ✅ Dependencies installed - restart FreeCAD to use AI providers\n" ) return True else: FreeCAD.Console.PrintWarning( "FreeCAD AI: ❌ Some dependencies failed to install\n" ) return False except ImportError as e: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Could not import dependency manager: {e}\n" ) return False except Exception as e: FreeCAD.Console.PrintError(f"FreeCAD AI: Dependency installation failed: {e}\n") return False try: # Strategy 1: Try absolute imports first try: from ai.providers.claude_provider import ClaudeProvider from ai.providers.gemini_provider import GeminiProvider from ai.providers.openrouter_provider import OpenRouterProvider AI_PROVIDERS_AVAILABLE = True FreeCAD.Console.PrintMessage( "FreeCAD AI: AI providers imported successfully via absolute imports\n" ) except ImportError as e: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Absolute AI providers import failed: {e}\n" ) AI_PROVIDERS_IMPORT_ERROR = str(e) # Check if it's a missing dependency issue if "aiohttp" in str(e): FreeCAD.Console.PrintWarning( "FreeCAD AI: Missing 'aiohttp' dependency detected\n" ) # Attempt automatic installation if attempt_dependency_installation(): FreeCAD.Console.PrintMessage( "FreeCAD AI: Dependencies installed - AI providers will be available after restart\n" ) else: FreeCAD.Console.PrintWarning( "FreeCAD AI: Automatic installation failed\n" ) FreeCAD.Console.PrintMessage("FreeCAD AI: Manual installation guide:\n") FreeCAD.Console.PrintMessage( " 1. Use the Dependencies tab in FreeCAD AI interface\n" ) FreeCAD.Console.PrintMessage( " 2. Or run: pip install aiohttp>=3.8.0\n" ) FreeCAD.Console.PrintMessage( " 3. Restart FreeCAD after installation\n" ) # Strategy 2: Try importing from the current directory structure try: sys.path.insert(0, addon_dir) from ai.providers.claude_provider import ClaudeProvider from ai.providers.gemini_provider import GeminiProvider from ai.providers.openrouter_provider import OpenRouterProvider AI_PROVIDERS_AVAILABLE = True FreeCAD.Console.PrintMessage( "FreeCAD AI: AI providers imported successfully via path modification\n" ) except ImportError as e2: FreeCAD.Console.PrintMessage( f"FreeCAD AI: AI providers import with path modification failed: {e2}\n" ) AI_PROVIDERS_IMPORT_ERROR = str(e2) # Strategy 3: Try importing individual providers with fallbacks try: # Create minimal fallback provider if individual imports fail class FallbackProvider: def __init__(self, name): self.name = name self.available = False def __str__(self): return f"Fallback{self.name}" try: from ai.providers.claude_provider import ClaudeProvider except ImportError: ClaudeProvider = FallbackProvider("ClaudeProvider") try: from ai.providers.gemini_provider import GeminiProvider except ImportError: GeminiProvider = FallbackProvider("GeminiProvider") try: from ai.providers.openrouter_provider import OpenRouterProvider except ImportError: OpenRouterProvider = FallbackProvider("OpenRouterProvider") AI_PROVIDERS_AVAILABLE = True # Partial availability with fallbacks FreeCAD.Console.PrintMessage( "FreeCAD AI: AI providers imported with fallbacks\n" ) except Exception as e3: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Even fallback AI providers import failed: {e3}\n" ) AI_PROVIDERS_AVAILABLE = False AI_PROVIDERS_IMPORT_ERROR = str(e3) except Exception as e: FreeCAD.Console.PrintError(f"FreeCAD AI: Critical AI providers import error: {e}\n") AI_PROVIDERS_AVAILABLE = False AI_PROVIDERS_IMPORT_ERROR = str(e) if not AI_PROVIDERS_AVAILABLE: FreeCAD.Console.PrintWarning( f"FreeCAD AI: AI providers not available - last error: {AI_PROVIDERS_IMPORT_ERROR}\n" ) # Provide specific guidance based on the error if AI_PROVIDERS_IMPORT_ERROR and "aiohttp" in AI_PROVIDERS_IMPORT_ERROR: FreeCAD.Console.PrintMessage("FreeCAD AI: 💡 To enable AI providers:\n") FreeCAD.Console.PrintMessage( " 1. Open FreeCAD AI interface and go to Dependencies tab\n" ) FreeCAD.Console.PrintMessage(" 2. Click 'Install Missing Dependencies'\n") FreeCAD.Console.PrintMessage(" 3. Restart FreeCAD\n") FreeCAD.Console.PrintMessage( " 4. Or manually install: pip install aiohttp>=3.8.0\n" ) # Print summary of import status with crash prevention try: # Print comprehensive import status summary with user guidance FreeCAD.Console.PrintMessage("=" * 60 + "\n") FreeCAD.Console.PrintMessage("FreeCAD AI: COMPONENT STATUS SUMMARY\n") FreeCAD.Console.PrintMessage("=" * 60 + "\n") # Core components FreeCAD.Console.PrintMessage("Core Components:\n") FreeCAD.Console.PrintMessage( f" Qt Bindings: {'✓ Available' if HAS_PYSIDE2 else '✗ Missing'}\n" ) FreeCAD.Console.PrintMessage( f" Tools: {'✓ Available' if TOOLS_AVAILABLE else '✗ Missing'}\n" ) # Optional components FreeCAD.Console.PrintMessage("Optional Components:\n") FreeCAD.Console.PrintMessage( f" Advanced Tools: {'✓ Available' if ADVANCED_TOOLS_AVAILABLE else '✗ Missing'}\n" ) FreeCAD.Console.PrintMessage( f" Resources: {'✓ Available' if RESOURCES_AVAILABLE else '✗ Missing'}\n" ) FreeCAD.Console.PrintMessage( f" Events: {'✓ Available' if EVENTS_AVAILABLE else '✗ Missing'}\n" ) FreeCAD.Console.PrintMessage( f" Clients: {'✓ Available' if CLIENTS_AVAILABLE else '✗ Missing'}\n" ) # API and AI components with detailed status api_status = "✓ Available" if API_AVAILABLE else "✗ Disabled" if not API_AVAILABLE and API_IMPORT_ERROR: if "Protocols with non-method members" in API_IMPORT_ERROR: api_status += " (FastAPI/Pydantic compatibility issue)" else: api_status += f" ({API_IMPORT_ERROR[:50]}...)" ai_status = "✓ Available" if AI_PROVIDERS_AVAILABLE else "✗ Missing" if not AI_PROVIDERS_AVAILABLE and AI_PROVIDERS_IMPORT_ERROR: if "aiohttp" in AI_PROVIDERS_IMPORT_ERROR: ai_status += " (missing aiohttp dependency)" else: ai_status += f" ({AI_PROVIDERS_IMPORT_ERROR[:50]}...)" FreeCAD.Console.PrintMessage("Network Components:\n") FreeCAD.Console.PrintMessage(f" API: {api_status}\n") FreeCAD.Console.PrintMessage(f" AI Providers: {ai_status}\n") # Overall status assessment critical_missing = [] if not HAS_PYSIDE2: critical_missing.append("Qt Bindings") if not TOOLS_AVAILABLE: critical_missing.append("Tools") optional_missing = [] if not API_AVAILABLE: optional_missing.append("API") if not AI_PROVIDERS_AVAILABLE: optional_missing.append("AI Providers") FreeCAD.Console.PrintMessage("\nOverall Status:\n") if not critical_missing: FreeCAD.Console.PrintMessage(" ✅ Core functionality: Available\n") else: FreeCAD.Console.PrintMessage( f" ❌ Core functionality: Missing {', '.join(critical_missing)}\n" ) if not optional_missing: FreeCAD.Console.PrintMessage(" ✅ Extended functionality: Fully available\n") else: FreeCAD.Console.PrintMessage( f" ⚠️ Extended functionality: Missing {', '.join(optional_missing)}\n" ) # User guidance if optional_missing: FreeCAD.Console.PrintMessage("\n💡 To enable missing functionality:\n") if not API_AVAILABLE and "Protocols with non-method members" in str( API_IMPORT_ERROR ): FreeCAD.Console.PrintMessage( " • API: Update FastAPI/Pydantic or use Python < 3.13\n" ) if not AI_PROVIDERS_AVAILABLE and "aiohttp" in str(AI_PROVIDERS_IMPORT_ERROR): FreeCAD.Console.PrintMessage( " • AI Providers: Install aiohttp dependency\n" ) FreeCAD.Console.PrintMessage( " - Use Dependencies tab in FreeCAD AI interface\n" ) FreeCAD.Console.PrintMessage(" - Or run: pip install aiohttp>=3.8.0\n") FreeCAD.Console.PrintMessage(" - Then restart FreeCAD\n") FreeCAD.Console.PrintMessage("=" * 60 + "\n") except Exception as e: FreeCAD.Console.PrintError(f"FreeCAD AI: Error printing import summary: {e}\n") class MCPShowInterfaceCommand: """Command to show the MCP interface.""" def GetResources(self): return { "Pixmap": "", # Icon path "MenuText": "Show MCP Interface", "ToolTip": "Show the FreeCAD AI interface", } def IsActive(self): return True @workbench_crash_safe("interface command activation") def Activated(self): """Activate the interface command with comprehensive error handling.""" try: FreeCAD.Console.PrintMessage("FreeCAD AI: Interface command activated\n") if not HAS_PYSIDE2: FreeCAD.Console.PrintWarning( "FreeCAD AI: No Qt bindings available - cannot show interface\n" ) return # Get main window safely try: main_window = FreeCADGui.getMainWindow() if not main_window: FreeCAD.Console.PrintError("FreeCAD AI: Cannot get main window\n") return except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Failed to get main window: {e}\n" ) return # Find existing dock widget dock_widget_found = False try: for widget in main_window.findChildren(QtWidgets.QDockWidget): if ( widget.windowTitle() == "FreeCAD AI" or widget.objectName() == "MCPIntegrationDockWidget" ): widget.show() widget.raise_() widget.activateWindow() dock_widget_found = True FreeCAD.Console.PrintMessage( "FreeCAD AI: Existing dock widget activated\n" ) break except Exception as e: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Error searching for existing dock widget: {e}\n" ) # If no dock widget found, show informative message if not dock_widget_found: try: QtWidgets.QMessageBox.information( main_window, "FreeCAD AI", "FreeCAD AI interface is loading...\n\n" "If the dock widget doesn't appear, try:\n" "1. Check the View menu for dock widgets\n" "2. Look on the right side of the interface\n" "3. Restart FreeCAD if issues persist", ) FreeCAD.Console.PrintMessage( "FreeCAD AI: Interface activation message shown\n" ) except Exception as e: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Could not show activation message: {e}\n" ) except Exception as e: FreeCAD.Console.PrintError(f"FreeCAD AI: Command activation failed: {e}\n") import traceback FreeCAD.Console.PrintError( f"FreeCAD AI: Activation traceback: {traceback.format_exc()}\n" ) # Register the command with comprehensive error handling try: if hasattr(FreeCADGui, "addCommand"): FreeCADGui.addCommand("MCP_ShowInterface", MCPShowInterfaceCommand()) FreeCAD.Console.PrintMessage( "FreeCAD AI: Command 'MCP_ShowInterface' registered successfully\n" ) else: FreeCAD.Console.PrintError("FreeCAD AI: FreeCADGui.addCommand not available\n") except Exception as e: FreeCAD.Console.PrintError(f"FreeCAD AI: Failed to register command: {e}\n") import traceback FreeCAD.Console.PrintError( f"FreeCAD AI: Command registration traceback: {traceback.format_exc()}\n" ) class MCPWorkbench(FreeCADGui.Workbench): """FreeCAD AI Workbench""" MenuText = "FreeCAD AI" ToolTip = "AI-powered CAD assistance integrated directly into FreeCAD" @workbench_crash_safe("workbench initialization") def __init__(self): """Initialize the MCP workbench.""" self.dock_widget = None # Persistent reference to dock widget try: self.__class__.Icon = self._get_icon_path() FreeCAD.Console.PrintMessage("FreeCAD AI Workbench: Instance created\n") except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI Workbench: Initialization error: {e}\n" ) @workbench_crash_safe("icon path retrieval") def _get_icon_path(self): """Get the workbench icon path.""" try: icon_path = os.path.join( os.path.dirname(__file__), "resources", "icons", "mcp_workbench.svg" ) if os.path.exists(icon_path): return icon_path except Exception as e: FreeCAD.Console.PrintWarning(f"FreeCAD AI: Icon path error: {e}\n") return "" @workbench_crash_safe("workbench GUI initialization") def Initialize(self): """Initialize the workbench GUI components with comprehensive error handling.""" try: FreeCAD.Console.PrintMessage( "FreeCAD AI Workbench: Starting initialization...\n" ) # Check if we have the required GUI components if not hasattr(FreeCADGui, "getMainWindow"): FreeCAD.Console.PrintError( "FreeCAD AI Workbench: FreeCADGui.getMainWindow not available\n" ) return # Define the toolbar commands using the standard FreeCAD method safe_gui_operation( lambda: self.appendToolbar("FreeCAD AI", ["MCP_ShowInterface"]), "toolbar creation", ) # Define menu structure safe_gui_operation( lambda: self.appendMenu("FreeCAD AI", ["MCP_ShowInterface"]), "menu creation", ) # Create dock widget with error handling safe_gui_operation( lambda: self._create_dock_widget_safe(), "dock widget creation" ) FreeCAD.Console.PrintMessage( "FreeCAD AI Workbench: Initialization complete\n" ) except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI Workbench: Initialization failed: {e}\n" ) import traceback FreeCAD.Console.PrintError( f"FreeCAD AI Workbench: Initialization traceback: {traceback.format_exc()}\n" ) @workbench_crash_safe("workbench activation") def Activated(self): """Called when the workbench is activated.""" try: FreeCAD.Console.PrintMessage("FreeCAD AI Workbench: Activated\n") except Exception as e: FreeCAD.Console.PrintError(f"FreeCAD AI Workbench: Activation error: {e}\n") @workbench_crash_safe("workbench deactivation") def Deactivated(self): """Called when the workbench is deactivated.""" try: FreeCAD.Console.PrintMessage("FreeCAD AI Workbench: Deactivated\n") except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI Workbench: Deactivation error: {e}\n" ) def GetClassName(self): """Return the workbench class name.""" return "Gui::PythonWorkbench" @workbench_crash_safe("dock widget creation") def _create_dock_widget_safe(self): """Create and show the dock widget with maximum safety and error handling.""" try: FreeCAD.Console.PrintMessage( "FreeCAD AI: Starting safe dock widget creation...\n" ) if not HAS_PYSIDE2: FreeCAD.Console.PrintMessage( "FreeCAD AI: Skipping dock widget creation - no Qt bindings\n" ) return # Get main window safely main_window = safe_gui_operation( lambda: FreeCADGui.getMainWindow(), "main window retrieval" ) if not main_window: FreeCAD.Console.PrintError( "FreeCAD AI: Cannot get main window for dock widget\n" ) return # Check if dock widget already exists and remove ALL instances (comprehensive cleanup) try: existing_widgets = [] for widget in main_window.findChildren(QtWidgets.QDockWidget): # Check both objectName and windowTitle to catch all possible instances if ( widget.objectName() == "MCPIntegrationDockWidget" or widget.windowTitle() == "FreeCAD AI" or ( hasattr(widget, "widget") and widget.widget() and hasattr(widget.widget(), "__class__") and "MCPMainWidget" in str(widget.widget().__class__) ) ): existing_widgets.append(widget) if existing_widgets: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Found {len(existing_widgets)} existing dock widget(s), performing comprehensive cleanup...\n" ) for i, widget in enumerate(existing_widgets): try: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Removing existing dock widget {i+1}: {widget.objectName()} / {widget.windowTitle()}\n" ) # Hide first to prevent visual glitches widget.hide() # Remove from main window main_window.removeDockWidget(widget) # Schedule for deletion widget.deleteLater() # Process events after each removal if ( hasattr(QtWidgets, "QApplication") and QtWidgets.QApplication.instance() ): QtWidgets.QApplication.processEvents() except Exception as e: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Error removing existing dock widget {i+1}: {e}\n" ) # Final event processing to ensure all deletions are complete if ( hasattr(QtWidgets, "QApplication") and QtWidgets.QApplication.instance() ): for _ in range( 3 ): # Multiple processing cycles for thorough cleanup QtWidgets.QApplication.processEvents() FreeCAD.Console.PrintMessage( "FreeCAD AI: Comprehensive dock widget cleanup completed\n" ) except Exception as e: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Error during comprehensive dock widget cleanup: {e}\n" ) # Import and create the main widget try: from gui.main_widget import MCPMainWidget FreeCAD.Console.PrintMessage( "FreeCAD AI: MCPMainWidget imported successfully\n" ) # Create main widget - this is a QDockWidget itself # IMPORTANT: Do NOT pass main_window as parent, use None dock_widget = safe_gui_operation( lambda: MCPMainWidget(None), "main widget creation" ) if not dock_widget: FreeCAD.Console.PrintError( "FreeCAD AI: Failed to create main widget\n" ) return dock_widget.setObjectName("MCPIntegrationDockWidget") dock_widget.setWindowTitle("FreeCAD AI") # Defensive: ensure QtCore.Qt.RightDockWidgetArea is valid dock_area = getattr(QtCore.Qt, "RightDockWidgetArea", None) if dock_area is None: # Fallback: 2 is the standard value for RightDockWidgetArea in Qt dock_area = 2 FreeCAD.Console.PrintWarning( "FreeCAD AI: QtCore.Qt.RightDockWidgetArea not found, using fallback value 2\n" ) else: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Using QtCore.Qt.RightDockWidgetArea={dock_area}\n" ) # Add to main window with error handling try: safe_gui_operation( lambda: main_window.addDockWidget(dock_area, dock_widget), "dock widget addition to main window", ) FreeCAD.Console.PrintMessage( "FreeCAD AI: Successfully added dock widget to main window\n" ) except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Exception in addDockWidget: {e}\n" ) FreeCAD.Console.PrintError( f"FreeCAD AI: dock_widget type: {type(dock_widget)}, repr: {repr(dock_widget)}\n" ) FreeCAD.Console.PrintError(f"FreeCAD AI: dock_area: {dock_area}\n") FreeCAD.Console.PrintError( f"FreeCAD AI: main_window type: {type(main_window)}, repr: {repr(main_window)}\n" ) # Clean up original dock widget before trying fallback FreeCAD.Console.PrintMessage( "FreeCAD AI: Cleaning up original dock widget before fallback...\n" ) try: dock_widget.deleteLater() if ( hasattr(QtWidgets, "QApplication") and QtWidgets.QApplication.instance() ): QtWidgets.QApplication.processEvents() except (AttributeError, RuntimeError, ImportError): # AttributeError: Missing Qt application methods # RuntimeError: Qt application in invalid state # ImportError: Qt modules not available pass # Fallback: Try to re-create the dock widget with main_window as parent FreeCAD.Console.PrintMessage( "FreeCAD AI: Attempting fallback dock widget creation...\n" ) try: from gui.main_widget import MCPMainWidget dock_widget = safe_gui_operation( lambda: MCPMainWidget(main_window), "main widget creation (main_window parent)", ) dock_widget.setObjectName("MCPIntegrationDockWidget") dock_widget.setWindowTitle("FreeCAD AI") safe_gui_operation( lambda: main_window.addDockWidget(dock_area, dock_widget), "dock widget addition to main window (main_window parent)", ) FreeCAD.Console.PrintMessage( "FreeCAD AI: Successfully added fallback dock widget.\n" ) except Exception as fallback_e: FreeCAD.Console.PrintError( f"FreeCAD AI: Fallback dock widget creation also failed: {fallback_e}\n" ) return # Show the dock widget safe_gui_operation(lambda: dock_widget.show(), "dock widget display") # Force dock widget to be visible and raised safe_gui_operation(lambda: dock_widget.raise_(), "dock widget raise") # Force layout update safe_gui_operation( lambda: dock_widget.updateGeometry(), "dock widget geometry update" ) self.dock_widget = dock_widget # Store persistent reference except ImportError as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Failed to import MCPMainWidget: {e}\n" ) # Create minimal fallback dock widget fallback_success = safe_gui_operation( lambda: self._create_fallback_dock_widget(main_window), "fallback dock widget creation", ) if fallback_success: FreeCAD.Console.PrintMessage( "FreeCAD AI: Fallback dock widget created\n" ) else: FreeCAD.Console.PrintError( "FreeCAD AI: Even fallback dock widget creation failed\n" ) except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Safe dock widget creation failed: {e}\n" ) import traceback FreeCAD.Console.PrintError( f"FreeCAD AI: Safe dock widget traceback: {traceback.format_exc()}\n" ) def _create_fallback_dock_widget(self, main_window): """Create a minimal fallback dock widget when main widget fails.""" try: fallback_widget = QtWidgets.QWidget() fallback_layout = QtWidgets.QVBoxLayout(fallback_widget) fallback_label = QtWidgets.QLabel( "FreeCAD AI\n\nMain widget failed to load.\n" "Check the console for details.\n\n" "Basic functionality may be limited." ) fallback_label.setStyleSheet("padding: 10px; color: #666;") fallback_layout.addWidget(fallback_label) dock_widget = QtWidgets.QDockWidget("FreeCAD AI", main_window) dock_widget.setObjectName("MCPIntegrationDockWidget") dock_widget.setWidget(fallback_widget) main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock_widget) dock_widget.show() self.dock_widget = dock_widget # Store persistent reference return True except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Fallback dock widget creation failed: {e}\n" ) return False

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/jango-blockchained/mcp-freecad'

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