Skip to main content
Glama
cam.py12.9 kB
import logging from typing import Any, Dict from ..base import ToolProvider, ToolResult, ToolSchema logger = logging.getLogger(__name__) class CAMToolProvider(ToolProvider): """Tool provider for CAM (Computer Aided Manufacturing) operations in FreeCAD.""" def __init__(self, freecad_app=None): """Initialize the CAM tool provider.""" self.app = freecad_app if self.app is None: try: import FreeCAD self.app = FreeCAD logger.info("Connected to FreeCAD") except ImportError: logger.warning( "Could not import FreeCAD. Make sure it's installed and in your Python path." ) self.app = None @property def tool_schema(self) -> ToolSchema: return ToolSchema( name="cam", description="Tools for CAM (Computer Aided Manufacturing) operations in FreeCAD", parameters={ "type": "object", "properties": { "action": { "type": "string", "enum": [ "create_job", "add_tool", "create_operation", "generate_gcode", "simulate", ], "description": "The CAM action to perform", }, "job_name": { "type": "string", "description": "Name of the CAM job", }, "base_object": { "type": "string", "description": "Name of the base object for machining", }, "operation_type": { "type": "string", "enum": [ "profile", "pocket", "drilling", "adaptive", "surface", ], "description": "Type of machining operation", }, "tool_diameter": { "type": "number", "description": "Tool diameter in mm", }, "spindle_speed": { "type": "number", "description": "Spindle speed in RPM", }, "feed_rate": { "type": "number", "description": "Feed rate in mm/min", }, "cut_depth": { "type": "number", "description": "Depth of cut in mm", }, }, "required": ["action"], }, returns={ "type": "object", "properties": { "status": {"type": "string"}, "result": {"type": "object"}, "error": {"type": "string"}, }, }, examples=[ { "action": "create_job", "job_name": "MyCAMJob", "base_object": "Part001", }, { "action": "add_tool", "job_name": "MyCAMJob", "tool_diameter": 6.0, "spindle_speed": 12000, }, ], ) async def execute_tool(self, tool_id: str, params: Dict[str, Any]) -> ToolResult: """Execute a CAM tool.""" if self.app is None: return self.format_result("error", error="FreeCAD not available") action = params.get("action") try: if action == "create_job": return await self._create_job(params) elif action == "add_tool": return await self._add_tool(params) elif action == "create_operation": return await self._create_operation(params) elif action == "generate_gcode": return await self._generate_gcode(params) elif action == "simulate": return await self._simulate(params) else: return self.format_result("error", error=f"Unknown action: {action}") except Exception as e: logger.error(f"CAM tool failed: {e}") return self.format_result("error", error=str(e)) async def _create_job(self, params: Dict[str, Any]) -> ToolResult: """Create a new CAM job.""" try: job_name = params.get("job_name", "CAMJob") base_object = params.get("base_object") doc = self._get_active_document() # Try to import Path workbench try: import PathScripts.PathJob as PathJob # Create job job = PathJob.Create(job_name) # Set base object if provided if base_object: base_obj = doc.getObject(base_object) if base_obj: job.Base = base_obj doc.recompute() return self.format_result( "success", result={ "job_name": job.Name, "label": job.Label, "base_object": base_object, "message": f"CAM job '{job_name}' created successfully", }, ) except ImportError: return self.format_result( "error", error="Path workbench not available. Please install FreeCAD with Path workbench.", ) except Exception as e: return self.format_result("error", error=f"Failed to create CAM job: {e}") async def _add_tool(self, params: Dict[str, Any]) -> ToolResult: """Add a tool to a CAM job.""" try: job_name = params.get("job_name") tool_diameter = params.get("tool_diameter", 6.0) spindle_speed = params.get("spindle_speed", 12000) feed_rate = params.get("feed_rate", 1000) doc = self._get_active_document() job = doc.getObject(job_name) if not job: return self.format_result( "error", error=f"CAM job '{job_name}' not found" ) try: import PathScripts.PathToolController as PathToolController # Create tool controller tc = PathToolController.Create("ToolController") # Set tool properties tc.Tool.Diameter = tool_diameter tc.SpindleSpeed = spindle_speed tc.HorizFeed = feed_rate tc.VertFeed = feed_rate / 2 # Typically slower for vertical feed # Add to job job.Tools.append(tc) doc.recompute() return self.format_result( "success", result={ "tool_controller": tc.Name, "tool_diameter": tool_diameter, "spindle_speed": spindle_speed, "feed_rate": feed_rate, "message": f"Tool added to job '{job_name}'", }, ) except ImportError: return self.format_result("error", error="Path workbench not available") except Exception as e: return self.format_result("error", error=f"Failed to add tool: {e}") async def _create_operation(self, params: Dict[str, Any]) -> ToolResult: """Create a machining operation.""" try: job_name = params.get("job_name") operation_type = params.get("operation_type", "profile") cut_depth = params.get("cut_depth", 1.0) doc = self._get_active_document() job = doc.getObject(job_name) if not job: return self.format_result( "error", error=f"CAM job '{job_name}' not found" ) try: pass # Create operation based on type if operation_type == "profile": import PathScripts.PathProfile as PathProfile op = PathProfile.Create("Profile") elif operation_type == "pocket": import PathScripts.PathPocket as PathPocket op = PathPocket.Create("Pocket") elif operation_type == "drilling": import PathScripts.PathDrilling as PathDrilling op = PathDrilling.Create("Drilling") else: return self.format_result( "error", error=f"Unsupported operation type: {operation_type}" ) # Set operation properties op.FinalDepth = -cut_depth # Add to job job.Operations.append(op) doc.recompute() return self.format_result( "success", result={ "operation_name": op.Name, "operation_type": operation_type, "cut_depth": cut_depth, "message": f"Operation '{operation_type}' added to job '{job_name}'", }, ) except ImportError: return self.format_result( "error", error="Path workbench operations not available" ) except Exception as e: return self.format_result("error", error=f"Failed to create operation: {e}") async def _generate_gcode(self, params: Dict[str, Any]) -> ToolResult: """Generate G-code for a CAM job.""" try: job_name = params.get("job_name") doc = self._get_active_document() job = doc.getObject(job_name) if not job: return self.format_result( "error", error=f"CAM job '{job_name}' not found" ) try: pass # Generate G-code gcode_lines = [] for op in job.Operations: if hasattr(op, "Path"): for command in op.Path.Commands: gcode_lines.append( command.Name + " " + " ".join( [f"{k}{v}" for k, v in command.Parameters.items()] ) ) gcode = "\n".join(gcode_lines) return self.format_result( "success", result={ "job_name": job_name, "gcode": gcode, "line_count": len(gcode_lines), "message": f"G-code generated for job '{job_name}'", }, ) except Exception as e: return self.format_result( "error", error=f"Failed to generate G-code: {e}" ) except Exception as e: return self.format_result("error", error=f"Failed to generate G-code: {e}") async def _simulate(self, params: Dict[str, Any]) -> ToolResult: """Simulate CAM operations.""" try: job_name = params.get("job_name") # This is a simplified simulation # Real implementation would use Path simulation tools return self.format_result( "success", result={ "job_name": job_name, "simulation_status": "completed", "message": f"Simulation completed for job '{job_name}' (simplified implementation)", "note": "Full simulation requires Path workbench simulation tools", }, ) except Exception as e: return self.format_result("error", error=f"Failed to simulate: {e}") def _get_active_document(self): """Get the active document or create a new one if none exists.""" if self.app.ActiveDocument: return self.app.ActiveDocument else: return self.app.newDocument("CAM_Document")

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