206 lines
6.5 KiB
Python
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,
|
|
}
|