kicad-mcp/kicad_mcp/resources/netlist_resources.py
Ryan Malloy bc0f3db97c
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
Implement comprehensive AI/LLM integration for KiCad MCP server
Add intelligent analysis and recommendation tools for KiCad designs:

## New AI Tools (kicad_mcp/tools/ai_tools.py)
- suggest_components_for_circuit: Smart component suggestions based on circuit analysis
- recommend_design_rules: Automated design rule recommendations for different technologies
- optimize_pcb_layout: PCB layout optimization for signal integrity, thermal, and cost
- analyze_design_completeness: Comprehensive design completeness analysis

## Enhanced Utilities
- component_utils.py: Add ComponentType enum and component classification functions
- pattern_recognition.py: Enhanced circuit pattern analysis and recommendations
- netlist_parser.py: Implement missing parse_netlist_file function for AI tools

## Key Features
- Circuit pattern recognition for power supplies, amplifiers, microcontrollers
- Technology-specific design rules (standard, HDI, RF, automotive)
- Layout optimization suggestions with implementation steps
- Component suggestion system with standard values and examples
- Design completeness scoring with actionable recommendations

## Server Integration
- Register AI tools in FastMCP server
- Integrate with existing KiCad utilities and file parsers
- Error handling and graceful fallbacks for missing data

Fixes ImportError that prevented server startup and enables advanced
AI-powered design assistance for KiCad projects.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 16:15:58 -06:00

263 lines
9.4 KiB
Python

"""
Netlist resources for KiCad schematics.
"""
import os
from mcp.server.fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import analyze_netlist, extract_netlist
def register_netlist_resources(mcp: FastMCP) -> None:
"""Register netlist-related resources with the MCP server.
Args:
mcp: The FastMCP server instance
"""
@mcp.resource("kicad://netlist/{schematic_path}")
def get_netlist_resource(schematic_path: str) -> str:
"""Get a formatted netlist report for a KiCad schematic.
Args:
schematic_path: Path to the KiCad schematic file (.kicad_sch)
Returns:
Markdown-formatted netlist report
"""
print(f"Generating netlist report for schematic: {schematic_path}")
if not os.path.exists(schematic_path):
return f"Schematic file not found: {schematic_path}"
try:
# Extract netlist information
netlist_data = extract_netlist(schematic_path)
if "error" in netlist_data:
return f"# Netlist Extraction Error\n\nError: {netlist_data['error']}"
# Analyze the netlist
analysis_results = analyze_netlist(netlist_data)
# Format as Markdown report
schematic_name = os.path.basename(schematic_path)
report = f"# Netlist Analysis for {schematic_name}\n\n"
# Overview section
report += "## Overview\n\n"
report += f"- **Components**: {netlist_data['component_count']}\n"
report += f"- **Nets**: {netlist_data['net_count']}\n"
if "total_pin_connections" in analysis_results:
report += f"- **Pin Connections**: {analysis_results['total_pin_connections']}\n"
report += "\n"
# Component Types section
if "component_types" in analysis_results and analysis_results["component_types"]:
report += "## Component Types\n\n"
for comp_type, count in analysis_results["component_types"].items():
report += f"- **{comp_type}**: {count}\n"
report += "\n"
# Power Nets section
if "power_nets" in analysis_results and analysis_results["power_nets"]:
report += "## Power Nets\n\n"
for net_name in analysis_results["power_nets"]:
report += f"- **{net_name}**\n"
report += "\n"
# Components section
components = netlist_data.get("components", {})
if components:
report += "## Component List\n\n"
report += "| Reference | Type | Value | Footprint |\n"
report += "|-----------|------|-------|----------|\n"
# Sort components by reference
for ref in sorted(components.keys()):
component = components[ref]
lib_id = component.get("lib_id", "Unknown")
value = component.get("value", "")
footprint = component.get("footprint", "")
report += f"| {ref} | {lib_id} | {value} | {footprint} |\n"
report += "\n"
# Nets section (limit to showing first 20 for readability)
nets = netlist_data.get("nets", {})
if nets:
report += "## Net List\n\n"
# Filter to show only the first 20 nets
net_items = list(nets.items())[:20]
for net_name, pins in net_items:
report += f"### Net: {net_name}\n\n"
if pins:
report += "**Connected Pins:**\n\n"
for pin in pins:
component = pin.get("component", "Unknown")
pin_num = pin.get("pin", "Unknown")
report += f"- {component}.{pin_num}\n"
else:
report += "*No connections found*\n"
report += "\n"
if len(nets) > 20:
report += f"*...and {len(nets) - 20} more nets*\n\n"
return report
except Exception as e:
return f"# Netlist Extraction Error\n\nError: {str(e)}"
@mcp.resource("kicad://project_netlist/{project_path}")
def get_project_netlist_resource(project_path: str) -> str:
"""Get a formatted netlist report for a KiCad project.
Args:
project_path: Path to the KiCad project file (.kicad_pro)
Returns:
Markdown-formatted netlist report
"""
print(f"Generating netlist report for project: {project_path}")
if not os.path.exists(project_path):
return f"Project not found: {project_path}"
# Get the schematic file
try:
files = get_project_files(project_path)
if "schematic" not in files:
return "Schematic file not found in project"
schematic_path = files["schematic"]
print(f"Found schematic file: {schematic_path}")
# Get the netlist resource for this schematic
return get_netlist_resource(schematic_path)
except Exception as e:
return f"# Netlist Extraction Error\n\nError: {str(e)}"
@mcp.resource("kicad://component/{schematic_path}/{component_ref}")
def get_component_resource(schematic_path: str, component_ref: str) -> str:
"""Get detailed information about a specific component and its connections.
Args:
schematic_path: Path to the KiCad schematic file (.kicad_sch)
component_ref: Component reference designator (e.g., R1)
Returns:
Markdown-formatted component report
"""
print(f"Generating component report for {component_ref} in schematic: {schematic_path}")
if not os.path.exists(schematic_path):
return f"Schematic file not found: {schematic_path}"
try:
# Extract netlist information
netlist_data = extract_netlist(schematic_path)
if "error" in netlist_data:
return f"# Component Analysis Error\n\nError: {netlist_data['error']}"
# Check if the component exists
components = netlist_data.get("components", {})
if component_ref not in components:
return (
f"# Component Not Found\n\nComponent {component_ref} was not found in the schematic.\n\n**Available Components**:\n\n"
+ "\n".join([f"- {ref}" for ref in sorted(components.keys())])
)
component_info = components[component_ref]
# Format as Markdown report
report = f"# Component Analysis: {component_ref}\n\n"
# Component Details section
report += "## Component Details\n\n"
report += f"- **Reference**: {component_ref}\n"
if "lib_id" in component_info:
report += f"- **Type**: {component_info['lib_id']}\n"
if "value" in component_info:
report += f"- **Value**: {component_info['value']}\n"
if "footprint" in component_info:
report += f"- **Footprint**: {component_info['footprint']}\n"
# Add other properties
if "properties" in component_info:
for prop_name, prop_value in component_info["properties"].items():
report += f"- **{prop_name}**: {prop_value}\n"
report += "\n"
# Pins section
if "pins" in component_info:
report += "## Pins\n\n"
for pin in component_info["pins"]:
report += f"- **Pin {pin['num']}**: {pin['name']}\n"
report += "\n"
# Connections section
report += "## Connections\n\n"
nets = netlist_data.get("nets", {})
connected_nets = []
for net_name, pins in nets.items():
# Check if any pin belongs to our component
for pin in pins:
if pin.get("component") == component_ref:
connected_nets.append(
{
"net_name": net_name,
"pin": pin.get("pin", "Unknown"),
"connections": [
p for p in pins if p.get("component") != component_ref
],
}
)
if connected_nets:
for net in connected_nets:
report += f"### Pin {net['pin']} - Net: {net['net_name']}\n\n"
if net["connections"]:
report += "**Connected To:**\n\n"
for conn in net["connections"]:
comp = conn.get("component", "Unknown")
pin = conn.get("pin", "Unknown")
report += f"- {comp}.{pin}\n"
else:
report += "*No connections*\n"
report += "\n"
else:
report += "*No connections found for this component*\n\n"
return report
except Exception as e:
return f"# Component Analysis Error\n\nError: {str(e)}"