Fix: rename workbench and prepare for release (#27)
* chore: rename workbench to FreecadRobustMCPBridge * fix: stdio cleanup and bug fix for JSON RPC * fix: update FreeCAD MCP bridge and tests * test: Fix GUI Integration tests and other issues * fix: unit tests in CI and a few other things * docs: generate GitHub pages site and link to it. * fix: General code improvements and DRY refactoring * chore: General cleanup * chore: small fixes * docs: cleanup * docs: fixes * docs: small corrections * docs: fix * test: release check * bump: workbench v0.6.0 * chore: bump macros to 0.6.0 * docs: Release improvements * refactor: improve DRYness of code
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""Robust MCP Bridge Workbench - Initialization.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
Copyright (c) 2025 Sean P. Kane (GitHub: spkane)
|
||||
|
||||
This module is executed when FreeCAD starts up. It handles initialization
|
||||
tasks for the Robust MCP Bridge workbench, including auto-start of the
|
||||
MCP bridge if configured. Works in both GUI and headless modes.
|
||||
|
||||
Note: Status bar updates are handled by InitGui.py since Qt operations
|
||||
must run on the main thread.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from freecad_mcp_bridge.bridge_utils import GuiWaiter
|
||||
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintMessage("Robust MCP Bridge: Init loaded\n")
|
||||
|
||||
# Global reference to GuiWaiter and auto-start timer to prevent garbage collection
|
||||
# Type annotations use Any for timer since it could be QTimer from PySide2 or PySide6
|
||||
_auto_start_timer: Any | None = None
|
||||
_gui_waiter: GuiWaiter | None = None
|
||||
|
||||
|
||||
def _auto_start_bridge() -> None:
|
||||
"""Auto-start the MCP bridge if configured in preferences.
|
||||
|
||||
This function is called via a deferred timer (GUI mode) or directly
|
||||
(headless mode) after FreeCAD finishes loading. It starts the bridge
|
||||
without requiring the workbench to be selected.
|
||||
"""
|
||||
try:
|
||||
from preferences import get_auto_start
|
||||
|
||||
if not get_auto_start():
|
||||
return
|
||||
|
||||
# Check if bridge is already running
|
||||
from commands import _mcp_plugin
|
||||
|
||||
if _mcp_plugin is not None and _mcp_plugin.is_running:
|
||||
return
|
||||
|
||||
FreeCAD.Console.PrintMessage(
|
||||
"Auto-starting MCP Bridge (configured in preferences)...\n"
|
||||
)
|
||||
|
||||
# Import and start the bridge directly
|
||||
from freecad_mcp_bridge.server import FreecadMCPPlugin
|
||||
from preferences import get_socket_port, get_xmlrpc_port
|
||||
|
||||
xmlrpc_port = get_xmlrpc_port()
|
||||
socket_port = get_socket_port()
|
||||
|
||||
plugin = FreecadMCPPlugin(
|
||||
host="localhost",
|
||||
port=socket_port,
|
||||
xmlrpc_port=xmlrpc_port,
|
||||
enable_xmlrpc=True,
|
||||
)
|
||||
plugin.start()
|
||||
|
||||
# Register plugin with commands module for restart detection
|
||||
from freecad_mcp_bridge.bridge_utils import register_mcp_plugin
|
||||
|
||||
register_mcp_plugin(plugin, xmlrpc_port, socket_port)
|
||||
|
||||
FreeCAD.Console.PrintMessage("\n")
|
||||
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
|
||||
FreeCAD.Console.PrintMessage("MCP Bridge started!\n")
|
||||
FreeCAD.Console.PrintMessage(f" - XML-RPC: localhost:{xmlrpc_port}\n")
|
||||
FreeCAD.Console.PrintMessage(f" - Socket: localhost:{socket_port}\n")
|
||||
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
|
||||
FreeCAD.Console.PrintMessage(
|
||||
"\nYou can now connect your MCP client (Claude Code, etc.) to FreeCAD.\n"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Failed to auto-start MCP Bridge: {e}\n")
|
||||
|
||||
|
||||
# Schedule auto-start after FreeCAD finishes loading
|
||||
# Strategy:
|
||||
# - If FreeCAD.GuiUp is True: Qt event loop is running, use timer for deferred start
|
||||
# - If FreeCAD.GuiUp is False but Qt is available: FreeCAD GUI is initializing.
|
||||
# Use GuiWaiter to wait for GuiUp to become True before starting.
|
||||
# This ensures the bridge uses Qt timer (not background thread) for queue processing.
|
||||
# - If Qt is not available: Pure headless mode, start bridge directly
|
||||
#
|
||||
# CRITICAL: We must wait for FreeCAD.GuiUp to be True before starting the bridge
|
||||
# in GUI mode. If we start when GuiUp is False, the bridge's _start_queue_processor()
|
||||
# will see GuiUp=False and use a background thread. Later, code executed on that
|
||||
# thread will try to do Qt operations, causing crashes (SIGABRT in QCocoaWindow).
|
||||
try:
|
||||
from preferences import get_auto_start
|
||||
|
||||
if get_auto_start():
|
||||
# Try to import Qt
|
||||
import contextlib
|
||||
|
||||
QtCore = None
|
||||
try:
|
||||
from PySide2 import QtCore # type: ignore[assignment, no-redef]
|
||||
except ImportError:
|
||||
with contextlib.suppress(ImportError):
|
||||
from PySide6 import QtCore # type: ignore[assignment, no-redef]
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# GUI is already up - use timer for deferred start
|
||||
if QtCore is not None:
|
||||
_auto_start_timer = QtCore.QTimer()
|
||||
_auto_start_timer.setSingleShot(True)
|
||||
_auto_start_timer.timeout.connect(_auto_start_bridge)
|
||||
_auto_start_timer.start(1000)
|
||||
else:
|
||||
# GUI is up but Qt import failed - start directly
|
||||
_auto_start_bridge()
|
||||
elif QtCore is not None:
|
||||
# GUI not ready yet, but Qt is available (FreeCAD starting in GUI mode)
|
||||
# Use GuiWaiter to wait for GuiUp to become True before starting
|
||||
from freecad_mcp_bridge.bridge_utils import GuiWaiter
|
||||
|
||||
_gui_waiter = GuiWaiter(
|
||||
callback=_auto_start_bridge,
|
||||
log_prefix="Robust MCP Bridge",
|
||||
timeout_error_extra=(
|
||||
"\nTo start the bridge manually, select the Robust MCP Bridge "
|
||||
"workbench\nand click 'Start MCP Bridge'.\n\n"
|
||||
),
|
||||
)
|
||||
_gui_waiter.start()
|
||||
else:
|
||||
# True headless mode - no Qt, no GUI
|
||||
_auto_start_bridge()
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"Could not set up auto-start: {e}\n")
|
||||
Reference in New Issue
Block a user