Files
freecad-robust-mcp-fc111/src/freecad_mcp/tools/documents.py
T

206 lines
6.5 KiB
Python

"""Document management tools for FreeCAD MCP server.
This module provides tools for managing FreeCAD documents:
creating, opening, saving, closing, and listing documents.
"""
from typing import Any
def register_document_tools(mcp, get_bridge) -> None:
"""Register document-related tools with the MCP server.
Args:
mcp: The FastMCP server instance.
get_bridge: Async function to get the active bridge.
"""
@mcp.tool()
async def list_documents() -> list[dict[str, Any]]:
"""List all open FreeCAD documents.
Returns:
List of dictionaries, each containing:
- name: Internal document name
- label: Display label
- path: File path (None if not saved)
- is_modified: Whether document has unsaved changes
- object_count: Number of objects in document
- active_object: Name of currently active object (None if none)
"""
bridge = await get_bridge()
docs = await bridge.get_documents()
return [
{
"name": doc.name,
"label": doc.label,
"path": doc.path,
"is_modified": doc.is_modified,
"object_count": len(doc.objects),
"active_object": doc.active_object,
}
for doc in docs
]
@mcp.tool()
async def get_active_document() -> dict[str, Any] | None:
"""Get the currently active FreeCAD document.
Returns:
Dictionary with document information, or None if no active document:
- name: Internal document name
- label: Display label
- path: File path (None if not saved)
- is_modified: Whether document has unsaved changes
- objects: List of object names
- active_object: Name of currently active object (None if none)
"""
bridge = await get_bridge()
doc = await bridge.get_active_document()
if doc is None:
return None
return {
"name": doc.name,
"label": doc.label,
"path": doc.path,
"is_modified": doc.is_modified,
"objects": doc.objects,
"active_object": doc.active_object,
}
@mcp.tool()
async def create_document(
name: str = "Unnamed",
label: str | None = None,
) -> dict[str, Any]:
"""Create a new FreeCAD document.
Args:
name: Internal document name (no spaces allowed, will be sanitized).
label: Display label (can contain spaces). Defaults to name.
Returns:
Dictionary with created document information:
- name: Internal document name
- label: Display label
- path: File path (None for new document)
"""
bridge = await get_bridge()
doc = await bridge.create_document(name, label)
return {
"name": doc.name,
"label": doc.label,
"path": doc.path,
}
@mcp.tool()
async def open_document(path: str) -> dict[str, Any]:
"""Open an existing FreeCAD document from file.
Args:
path: Full path to the .FCStd file to open.
Returns:
Dictionary with opened document information:
- name: Internal document name
- label: Display label
- path: File path
- objects: List of object names
Raises:
FileNotFoundError: If the file doesn't exist.
ValueError: If the file is not a valid FreeCAD document.
"""
bridge = await get_bridge()
doc = await bridge.open_document(path)
return {
"name": doc.name,
"label": doc.label,
"path": doc.path,
"objects": doc.objects,
}
@mcp.tool()
async def save_document(
doc_name: str | None = None,
path: str | None = None,
) -> dict[str, Any]:
"""Save a FreeCAD document.
Args:
doc_name: Name of document to save. Uses active document if None.
path: File path to save to. Uses existing path if None.
Required for new (unsaved) documents.
Returns:
Dictionary with save result:
- success: Whether save was successful
- path: Path where document was saved
Raises:
ValueError: If document not found or no path specified for new doc.
"""
bridge = await get_bridge()
saved_path = await bridge.save_document(doc_name, path)
return {
"success": True,
"path": saved_path,
}
@mcp.tool()
async def close_document(
doc_name: str | None = None,
save_changes: bool = False,
) -> dict[str, Any]:
"""Close a FreeCAD document.
Args:
doc_name: Name of document to close. Uses active document if None.
save_changes: Whether to save changes before closing. Defaults to False.
Returns:
Dictionary with close result:
- success: Whether close was successful
- saved: Whether document was saved before closing
"""
bridge = await get_bridge()
saved = False
if save_changes:
try:
await bridge.save_document(doc_name)
saved = True
except Exception:
pass
await bridge.close_document(doc_name)
return {
"success": True,
"saved": saved,
}
@mcp.tool()
async def recompute_document(doc_name: str | None = None) -> dict[str, Any]:
"""Recompute a FreeCAD document to update all dependent objects.
Args:
doc_name: Name of document to recompute. Uses active document if None.
Returns:
Dictionary with recompute result:
- success: Whether recompute was successful
"""
bridge = await get_bridge()
code = f"""
doc = FreeCAD.ActiveDocument if {doc_name!r} is None else FreeCAD.getDocument({doc_name!r})
if doc is None:
raise ValueError("No document found")
doc.recompute()
_result_ = True
"""
result = await bridge.execute_python(code)
return {
"success": result.success,
"error": result.error_traceback if not result.success else None,
}