kicad-mcp/mckicad/tools/model3d_tools.py
Ryan Malloy 687e14bd11 Rename project from kicad-mcp to mckicad
Rename source directory kicad_mcp/ → mckicad/, update all imports,
pyproject.toml metadata, documentation references, Makefile targets,
and .gitignore paths. All 195 tests pass.
2026-02-13 00:53:59 -07:00

336 lines
13 KiB
Python

"""
3D Model Analysis Tools for KiCad MCP Server.
Provides MCP tools for analyzing 3D models, mechanical constraints,
and visualization data from KiCad PCB files.
"""
import json
from typing import Any
from fastmcp import FastMCP
from mckicad.utils.model3d_analyzer import (
Model3DAnalyzer,
analyze_pcb_3d_models,
get_mechanical_constraints,
)
from mckicad.utils.path_validator import validate_kicad_file
def register_model3d_tools(mcp: FastMCP) -> None:
"""Register 3D model analysis tools with the MCP server."""
@mcp.tool()
def analyze_3d_models(pcb_file_path: str) -> dict[str, Any]:
"""
Analyze 3D models and mechanical aspects of a KiCad PCB file.
Extracts 3D component information, board dimensions, clearance violations,
and generates data suitable for 3D visualization.
Args:
pcb_file_path: Full path to the .kicad_pcb file to analyze
Returns:
Dictionary containing 3D analysis results including:
- board_dimensions: Physical board size and outline
- components: List of 3D components with positions and models
- height_analysis: Component height statistics
- clearance_violations: Detected mechanical issues
- stats: Summary statistics
Examples:
analyze_3d_models("/path/to/my_board.kicad_pcb")
analyze_3d_models("~/kicad_projects/robot_controller/robot.kicad_pcb")
"""
try:
# Validate the PCB file path
validated_path = validate_kicad_file(pcb_file_path, "pcb")
# Perform 3D analysis
result = analyze_pcb_3d_models(validated_path)
return {
"success": True,
"pcb_file": validated_path,
"analysis": result
}
except Exception as e:
return {
"success": False,
"error": str(e),
"pcb_file": pcb_file_path
}
@mcp.tool()
def check_mechanical_constraints(pcb_file_path: str) -> dict[str, Any]:
"""
Check mechanical constraints and clearances in a KiCad PCB.
Performs comprehensive mechanical analysis including component clearances,
board edge distances, height constraints, and identifies potential
manufacturing or assembly issues.
Args:
pcb_file_path: Path to the .kicad_pcb file to analyze
Returns:
Dictionary containing mechanical analysis results:
- constraints: List of constraint violations
- clearance_violations: Detailed clearance issues
- board_dimensions: Physical board properties
- recommendations: Suggested improvements
"""
try:
validated_path = validate_kicad_file(pcb_file_path, "pcb")
# Perform mechanical analysis
analysis = get_mechanical_constraints(validated_path)
# Generate recommendations
recommendations = []
if analysis.height_analysis["max"] > 5.0:
recommendations.append("Consider using lower profile components to reduce board height")
if len(analysis.clearance_violations) > 0:
recommendations.append("Review component placement to resolve clearance violations")
if analysis.board_dimensions.width > 80 or analysis.board_dimensions.height > 80:
recommendations.append("Large board size may increase manufacturing costs")
return {
"success": True,
"pcb_file": validated_path,
"constraints": analysis.mechanical_constraints,
"clearance_violations": [
{
"type": v["type"],
"components": [v.get("component1", ""), v.get("component2", ""), v.get("component", "")],
"distance": v["distance"],
"required": v["required_clearance"],
"severity": v["severity"]
}
for v in analysis.clearance_violations
],
"board_dimensions": {
"width_mm": analysis.board_dimensions.width,
"height_mm": analysis.board_dimensions.height,
"thickness_mm": analysis.board_dimensions.thickness,
"area_mm2": analysis.board_dimensions.width * analysis.board_dimensions.height
},
"height_analysis": analysis.height_analysis,
"recommendations": recommendations,
"component_count": len(analysis.components)
}
except Exception as e:
return {
"success": False,
"error": str(e),
"pcb_file": pcb_file_path
}
@mcp.tool()
def generate_3d_visualization_json(pcb_file_path: str, output_path: str = None) -> dict[str, Any]:
"""
Generate JSON data file for 3D visualization of PCB.
Creates a structured JSON file containing all necessary data for
3D visualization tools, including component positions, board outline,
and model references.
Args:
pcb_file_path: Path to the .kicad_pcb file
output_path: Optional path for output JSON file (defaults to same dir as PCB)
Returns:
Dictionary with generation results and file path
"""
try:
validated_path = validate_kicad_file(pcb_file_path, "pcb")
# Generate visualization data
viz_data = analyze_pcb_3d_models(validated_path)
# Determine output path
if not output_path:
output_path = validated_path.replace('.kicad_pcb', '_3d_viz.json')
# Save visualization data
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(viz_data, f, indent=2)
return {
"success": True,
"pcb_file": validated_path,
"output_file": output_path,
"component_count": viz_data.get("stats", {}).get("total_components", 0),
"models_found": viz_data.get("stats", {}).get("components_with_3d_models", 0),
"board_size": f"{viz_data.get('board_dimensions', {}).get('width', 0):.1f}x{viz_data.get('board_dimensions', {}).get('height', 0):.1f}mm"
}
except Exception as e:
return {
"success": False,
"error": str(e),
"pcb_file": pcb_file_path
}
@mcp.tool()
def component_height_distribution(pcb_file_path: str) -> dict[str, Any]:
"""
Analyze the height distribution of components on a PCB.
Provides detailed analysis of component heights, useful for
determining enclosure requirements and assembly considerations.
Args:
pcb_file_path: Path to the .kicad_pcb file
Returns:
Height distribution analysis with statistics and component breakdown
"""
try:
validated_path = validate_kicad_file(pcb_file_path, "pcb")
analyzer = Model3DAnalyzer(validated_path)
components = analyzer.extract_3d_components()
height_analysis = analyzer.analyze_component_heights(components)
# Categorize components by height
height_categories = {
"very_low": [], # < 1mm
"low": [], # 1-2mm
"medium": [], # 2-5mm
"high": [], # 5-10mm
"very_high": [] # > 10mm
}
for comp in components:
height = analyzer._estimate_component_height(comp)
if height < 1.0:
height_categories["very_low"].append((comp.reference, height))
elif height < 2.0:
height_categories["low"].append((comp.reference, height))
elif height < 5.0:
height_categories["medium"].append((comp.reference, height))
elif height < 10.0:
height_categories["high"].append((comp.reference, height))
else:
height_categories["very_high"].append((comp.reference, height))
return {
"success": True,
"pcb_file": validated_path,
"height_statistics": height_analysis,
"height_categories": {
category: [{"component": ref, "height_mm": height}
for ref, height in components]
for category, components in height_categories.items()
},
"tallest_components": sorted(
[(comp.reference, analyzer._estimate_component_height(comp))
for comp in components],
key=lambda x: x[1], reverse=True
)[:10], # Top 10 tallest components
"enclosure_requirements": {
"minimum_height_mm": height_analysis["max"] + 2.0, # Add 2mm clearance
"recommended_height_mm": height_analysis["max"] + 5.0 # Add 5mm clearance
}
}
except Exception as e:
return {
"success": False,
"error": str(e),
"pcb_file": pcb_file_path
}
@mcp.tool()
def check_assembly_feasibility(pcb_file_path: str) -> dict[str, Any]:
"""
Analyze PCB assembly feasibility and identify potential issues.
Checks for component accessibility, assembly sequence issues,
and manufacturing constraints that could affect PCB assembly.
Args:
pcb_file_path: Path to the .kicad_pcb file
Returns:
Assembly feasibility analysis with issues and recommendations
"""
try:
validated_path = validate_kicad_file(pcb_file_path, "pcb")
analyzer = Model3DAnalyzer(validated_path)
mechanical_analysis = analyzer.perform_mechanical_analysis()
components = mechanical_analysis.components
assembly_issues = []
assembly_warnings = []
# Check for components too close to board edge
for comp in components:
edge_distance = analyzer._distance_to_board_edge(
comp, mechanical_analysis.board_dimensions
)
if edge_distance < 1.0: # Less than 1mm from edge
assembly_warnings.append({
"component": comp.reference,
"issue": f"Component only {edge_distance:.2f}mm from board edge",
"recommendation": "Consider moving component away from edge for easier assembly"
})
# Check for very small components that might be hard to place
small_component_footprints = ["0201", "0402"]
for comp in components:
if any(size in (comp.footprint or "") for size in small_component_footprints):
assembly_warnings.append({
"component": comp.reference,
"issue": f"Very small footprint {comp.footprint}",
"recommendation": "Verify pick-and-place machine compatibility"
})
# Check component density
board_area = (mechanical_analysis.board_dimensions.width *
mechanical_analysis.board_dimensions.height)
component_density = len(components) / (board_area / 100) # Components per cm²
if component_density > 5.0:
assembly_warnings.append({
"component": "Board",
"issue": f"High component density: {component_density:.1f} components/cm²",
"recommendation": "Consider larger board or fewer components for easier assembly"
})
return {
"success": True,
"pcb_file": validated_path,
"assembly_feasible": len(assembly_issues) == 0,
"assembly_issues": assembly_issues,
"assembly_warnings": assembly_warnings,
"component_density": component_density,
"board_utilization": {
"component_count": len(components),
"board_area_mm2": board_area,
"density_per_cm2": component_density
},
"recommendations": [
"Review component placement for optimal assembly sequence",
"Ensure adequate fiducial markers for automated assembly",
"Consider component orientation for consistent placement direction"
] if assembly_warnings else ["PCB appears suitable for standard assembly processes"]
}
except Exception as e:
return {
"success": False,
"error": str(e),
"pcb_file": pcb_file_path
}