"""MCP tool definitions for CAD review.""" from __future__ import annotations import logging from pathlib import Path logger = logging.getLogger(__name__) def register_tools(mcp): """Register all MCP tools.""" @mcp.tool() def get_assembly_tree(job_id: str) -> str: """Get the assembly tree structure for a loaded STEP file. Args: job_id: The job ID from uploading a STEP file. """ from src.api.router import get_manager manager = get_manager() job = manager.get_job(job_id) if job is None: return f"Error: Job not found: {job_id}" if job.assembly_tree is None: return f"Error: Assembly tree not available yet (status: {job.status})" import json return json.dumps(job.assembly_tree.to_dict(), indent=2) @mcp.tool() def get_part_info(job_id: str, part_id: str) -> str: """Get detailed information about a specific part. Args: job_id: The job ID. part_id: The part ID from the assembly tree. """ from src.api.router import get_manager manager = get_manager() job = manager.get_job(job_id) if job is None: return f"Error: Job not found: {job_id}" if job.assembly_tree is None: return "Error: Assembly tree not available yet" node = job.assembly_tree.find_by_id(part_id) if node is None: return f"Error: Part not found: {part_id}" import json info = node.to_dict() mesh = job.part_meshes.get(part_id) if mesh: info["mesh_vertices"] = len(mesh.vertices) info["mesh_triangles"] = len(mesh.triangles) return json.dumps(info, indent=2) @mcp.tool() def measure_part_distance(job_id: str, part_a_id: str, part_b_id: str) -> str: """Measure the minimum distance between two parts. Args: job_id: The job ID. part_a_id: ID of the first part. part_b_id: ID of the second part. """ from src.api.router import get_manager from src.geometry.measurement import measure_distance manager = get_manager() job = manager.get_job(job_id) if job is None: return f"Error: Job not found: {job_id}" if job.assembly_tree is None: return "Error: Assembly tree not available yet" node_a = job.assembly_tree.find_by_id(part_a_id) node_b = job.assembly_tree.find_by_id(part_b_id) if node_a is None or node_a.shape is None: return f"Error: Part not found or has no shape: {part_a_id}" if node_b is None or node_b.shape is None: return f"Error: Part not found or has no shape: {part_b_id}" result = measure_distance(node_a.shape, node_b.shape) import json return json.dumps({ "distance_mm": result.distance_mm, "point_a": result.point_a, "point_b": result.point_b, }, indent=2) @mcp.tool() def check_compliance_rules(job_id: str) -> str: """Run compliance checks on the loaded assembly. Args: job_id: The job ID. """ from src.api.router import get_manager from src.compliance.kmvss_checker import check_compliance from src.compliance.rule_loader import load_rules manager = get_manager() job = manager.get_job(job_id) if job is None: return f"Error: Job not found: {job_id}" if job.assembly_tree is None: return "Error: Assembly tree not available yet" rules_path = Path(__file__).parent.parent.parent / "config" / "kmvss_rules.yaml" rules = load_rules(rules_path) results = check_compliance(rules, job.assembly_tree) import json return json.dumps([{ "rule": r.rule_name, "passed": r.passed, "message": r.message, "severity": r.severity, } for r in results], indent=2) @mcp.tool() def get_collision_report(job_id: str) -> str: """Get a collision/proximity report for all parts. Args: job_id: The job ID. """ from src.api.router import get_manager from src.geometry.measurement import scan_proximity manager = get_manager() job = manager.get_job(job_id) if job is None: return f"Error: Job not found: {job_id}" if job.assembly_tree is None: return "Error: Assembly tree not available yet" parts = [] for leaf in job.assembly_tree.iter_leaves(): if leaf.shape is not None and not leaf.shape.IsNull(): parts.append({"id": leaf.id, "name": leaf.name, "shape": leaf.shape}) results = scan_proximity(parts) import json return json.dumps([{ "part_a": r.part_a_name, "part_b": r.part_b_name, "distance_mm": r.min_distance_mm, "status": r.status, } for r in results], indent=2)