Improve README clarity and remove duplicate test directory
Major improvements: - Added "What is MCP?" explanation for newcomers - Created prominent "Prerequisites" section emphasizing KiCAD setup - Listed all Python requirements inline with descriptions - Fixed GitHub URL from placeholder to actual repo - Removed duplicate test/ directory (keeping tests/ for pytest) - Made KiCAD installation steps more visible and clear - Added verification command for KiCAD Python module - Updated all config example paths to match actual repo name This makes it much clearer for new users how to install and configure the MCP server, with special emphasis on the critical KiCAD requirement. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
83
README.md
83
README.md
@@ -2,6 +2,16 @@
|
||||
|
||||
KiCAD MCP is a Model Context Protocol (MCP) implementation that enables Large Language Models (LLMs) like Claude to directly interact with KiCAD for printed circuit board design. It creates a standardized communication bridge between AI assistants and the KiCAD PCB design software, allowing for natural language control of advanced PCB design operations.
|
||||
|
||||
## What is MCP?
|
||||
|
||||
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard from Anthropic that allows AI assistants like Claude to securely connect to external tools and data sources. Think of it as a universal adapter that lets Claude interact with your local software - in this case, KiCAD.
|
||||
|
||||
**With this MCP server, you can:**
|
||||
- Design PCBs by talking to Claude in natural language
|
||||
- Automate complex KiCAD operations through AI assistance
|
||||
- Get real-time feedback as Claude creates and modifies your boards
|
||||
- Leverage AI to handle tedious PCB design tasks
|
||||
|
||||
## NEW FEATURES
|
||||
|
||||
### Schematic Generation
|
||||
@@ -96,16 +106,53 @@ This enables a natural language-driven PCB design workflow where complex operati
|
||||
- **Python KiCAD Interface**: Handles actual KiCAD operations via pcbnew Python API and kicad-skip library with comprehensive error handling
|
||||
- **Modular Design**: Organizes functionality by domains (project, schematic, board, component, routing) for maintainability and extensibility
|
||||
|
||||
## System Requirements
|
||||
## Prerequisites - READ THIS FIRST!
|
||||
|
||||
- **KiCAD 9.0 or higher** (must be fully installed with Python module)
|
||||
- **Node.js v18 or higher** and npm
|
||||
- **Python 3.10 or higher** with pip
|
||||
- **Cline** (VSCode extension) or another MCP-compatible client
|
||||
- **Operating System**:
|
||||
- **Linux** (Ubuntu 22.04+, Fedora, Arch) - Primary platform
|
||||
- **Windows 10/11** - Fully supported
|
||||
- **macOS** - Experimental (untested)
|
||||
Before installing this MCP server, you **MUST** have:
|
||||
|
||||
### 1. KiCAD 9.0 or Higher (REQUIRED!)
|
||||
|
||||
**This is the most critical requirement.** Without KiCAD properly installed with its Python module, this MCP server will not work.
|
||||
|
||||
- **Download:** [kicad.org/download](https://www.kicad.org/download/)
|
||||
- **Verify Python module:** After installing, run:
|
||||
```bash
|
||||
python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())"
|
||||
```
|
||||
If this fails, your KiCAD installation is incomplete.
|
||||
|
||||
### 2. Python 3.10 or Higher
|
||||
|
||||
**Required Python packages:**
|
||||
```
|
||||
kicad-skip>=0.1.0 # Schematic manipulation
|
||||
Pillow>=9.0.0 # Image processing for board rendering
|
||||
cairosvg>=2.7.0 # SVG rendering
|
||||
colorlog>=6.7.0 # Colored logging
|
||||
pydantic>=2.5.0 # Data validation
|
||||
requests>=2.31.0 # HTTP requests (for future API features)
|
||||
python-dotenv>=1.0.0 # Environment management
|
||||
```
|
||||
|
||||
These will be installed automatically via `pip install -r requirements.txt`
|
||||
|
||||
### 3. Node.js v18 or Higher
|
||||
|
||||
- **Download:** [nodejs.org](https://nodejs.org/)
|
||||
- **Verify:** Run `node --version` and `npm --version`
|
||||
|
||||
### 4. An MCP-Compatible Client
|
||||
|
||||
Choose one:
|
||||
- **[Claude Desktop](https://claude.ai/download)** - Official Anthropic desktop app
|
||||
- **[Claude Code](https://docs.claude.com/claude-code)** - Official Anthropic CLI tool
|
||||
- **[Cline](https://github.com/cline/cline)** - Popular VSCode extension
|
||||
|
||||
### 5. Operating System
|
||||
|
||||
- **Linux** (Ubuntu 22.04+, Fedora, Arch) - Primary platform, fully tested
|
||||
- **Windows 10/11** - Fully supported
|
||||
- **macOS** - Experimental (untested, please report issues!)
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -141,8 +188,8 @@ npm --version
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/yourusername/kicad-mcp-server.git
|
||||
cd kicad-mcp-server
|
||||
git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
|
||||
cd KiCAD-MCP-Server
|
||||
|
||||
# Install Node.js dependencies
|
||||
npm install
|
||||
@@ -168,7 +215,7 @@ npm run build
|
||||
"mcpServers": {
|
||||
"kicad": {
|
||||
"command": "node",
|
||||
"args": ["/home/YOUR_USERNAME/kicad-mcp-server/dist/index.js"],
|
||||
"args": ["/home/YOUR_USERNAME/KiCAD-MCP-Server/dist/index.js"],
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"PYTHONPATH": "/usr/lib/kicad/lib/python3/dist-packages",
|
||||
@@ -221,8 +268,8 @@ pytest tests/
|
||||
|
||||
```powershell
|
||||
# Clone repository
|
||||
git clone https://github.com/yourusername/kicad-mcp-server.git
|
||||
cd kicad-mcp-server
|
||||
git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
|
||||
cd KiCAD-MCP-Server
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
@@ -246,7 +293,7 @@ npm run build
|
||||
"mcpServers": {
|
||||
"kicad": {
|
||||
"command": "C:\\Program Files\\nodejs\\node.exe",
|
||||
"args": ["C:\\path\\to\\kicad-mcp-server\\dist\\index.js"],
|
||||
"args": ["C:\\Users\\YOUR_USERNAME\\KiCAD-MCP-Server\\dist\\index.js"],
|
||||
"env": {
|
||||
"PYTHONPATH": "C:\\Program Files\\KiCad\\9.0\\lib\\python3\\dist-packages"
|
||||
}
|
||||
@@ -281,8 +328,8 @@ npm --version
|
||||
### Step 3: Clone and Build
|
||||
|
||||
```bash
|
||||
git clone https://github.com/yourusername/kicad-mcp-server.git
|
||||
cd kicad-mcp-server
|
||||
git clone https://github.com/mixelpixx/KiCAD-MCP-Server.git
|
||||
cd KiCAD-MCP-Server
|
||||
npm install
|
||||
pip3 install -r requirements.txt
|
||||
npm run build
|
||||
@@ -297,7 +344,7 @@ Edit `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-d
|
||||
"mcpServers": {
|
||||
"kicad": {
|
||||
"command": "node",
|
||||
"args": ["/Users/YOUR_USERNAME/kicad-mcp-server/dist/index.js"],
|
||||
"args": ["/Users/YOUR_USERNAME/KiCAD-MCP-Server/dist/index.js"],
|
||||
"env": {
|
||||
"PYTHONPATH": "/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages"
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
(kicad_pcb
|
||||
(version 20241229)
|
||||
(generator "pcbnew")
|
||||
(generator_version "9.0")
|
||||
(general
|
||||
(thickness 1.6)
|
||||
(legacy_teardrops no)
|
||||
)
|
||||
(paper "A4")
|
||||
(title_block
|
||||
(title "TestProject")
|
||||
(date "2025-04-26")
|
||||
)
|
||||
(layers
|
||||
(0 "F.Cu" signal)
|
||||
(2 "B.Cu" signal)
|
||||
(9 "F.Adhes" user "F.Adhesive")
|
||||
(11 "B.Adhes" user "B.Adhesive")
|
||||
(13 "F.Paste" user)
|
||||
(15 "B.Paste" user)
|
||||
(5 "F.SilkS" user "F.Silkscreen")
|
||||
(7 "B.SilkS" user "B.Silkscreen")
|
||||
(1 "F.Mask" user)
|
||||
(3 "B.Mask" user)
|
||||
(17 "Dwgs.User" user "User.Drawings")
|
||||
(19 "Cmts.User" user "User.Comments")
|
||||
(21 "Eco1.User" user "User.Eco1")
|
||||
(23 "Eco2.User" user "User.Eco2")
|
||||
(25 "Edge.Cuts" user)
|
||||
(27 "Margin" user)
|
||||
(31 "F.CrtYd" user "F.Courtyard")
|
||||
(29 "B.CrtYd" user "B.Courtyard")
|
||||
(35 "F.Fab" user)
|
||||
(33 "B.Fab" user)
|
||||
(39 "User.1" user)
|
||||
(41 "User.2" user)
|
||||
(43 "User.3" user)
|
||||
(45 "User.4" user)
|
||||
)
|
||||
(setup
|
||||
(pad_to_mask_clearance 0)
|
||||
(allow_soldermask_bridges_in_footprints no)
|
||||
(tenting front back)
|
||||
(pcbplotparams
|
||||
(layerselection 0x00000000_00000000_55555555_5755f5ff)
|
||||
(plot_on_all_layers_selection 0x00000000_00000000_00000000_00000000)
|
||||
(disableapertmacros no)
|
||||
(usegerberextensions no)
|
||||
(usegerberattributes yes)
|
||||
(usegerberadvancedattributes yes)
|
||||
(creategerberjobfile yes)
|
||||
(dashed_line_dash_ratio 12.000000)
|
||||
(dashed_line_gap_ratio 3.000000)
|
||||
(svgprecision 4)
|
||||
(plotframeref no)
|
||||
(mode 1)
|
||||
(useauxorigin no)
|
||||
(hpglpennumber 1)
|
||||
(hpglpenspeed 20)
|
||||
(hpglpendiameter 15.000000)
|
||||
(pdf_front_fp_property_popups yes)
|
||||
(pdf_back_fp_property_popups yes)
|
||||
(pdf_metadata yes)
|
||||
(pdf_single_document no)
|
||||
(dxfpolygonmode yes)
|
||||
(dxfimperialunits yes)
|
||||
(dxfusepcbnewfont yes)
|
||||
(psnegative no)
|
||||
(psa4output no)
|
||||
(plot_black_and_white yes)
|
||||
(sketchpadsonfab no)
|
||||
(plotpadnumbers no)
|
||||
(hidednponfab no)
|
||||
(sketchdnponfab yes)
|
||||
(crossoutdnponfab yes)
|
||||
(subtractmaskfromsilk no)
|
||||
(outputformat 1)
|
||||
(mirror no)
|
||||
(drillshape 1)
|
||||
(scaleselection 1)
|
||||
(outputdirectory "")
|
||||
)
|
||||
)
|
||||
(net 0 "")
|
||||
(embedded_fonts no)
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"board": {
|
||||
"filename": "TestProject.kicad_pcb"
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple script to check if the skip module is available
|
||||
"""
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
print("Python executable:", sys.executable)
|
||||
print("Python version:", sys.version)
|
||||
|
||||
try:
|
||||
print("Attempting to import skip module...")
|
||||
import skip
|
||||
print("Successfully imported skip module!")
|
||||
print("Skip module version:", getattr(skip, "__version__", "Unknown"))
|
||||
print("Skip module path:", skip.__file__)
|
||||
|
||||
# Check if Schematic class is available
|
||||
print("\nChecking for Schematic class...")
|
||||
if hasattr(skip, "Schematic"):
|
||||
print("Schematic class is available!")
|
||||
|
||||
# Create a schematic object
|
||||
print("Creating schematic object...")
|
||||
sch = skip.Schematic()
|
||||
print("Successfully created schematic object!")
|
||||
|
||||
# Check for methods and attributes we use
|
||||
print("\nChecking schematic methods/attributes:")
|
||||
print("- add_symbol method:", hasattr(sch, "add_symbol"))
|
||||
print("- add_wire method:", hasattr(sch, "add_wire"))
|
||||
print("- save method:", hasattr(sch, "save"))
|
||||
print("- version attribute:", hasattr(sch, "version"))
|
||||
|
||||
else:
|
||||
print("ERROR: Schematic class is NOT available in the skip module!")
|
||||
|
||||
# List all available classes/functions in the skip module
|
||||
print("\nAvailable members in skip module:")
|
||||
for name in dir(skip):
|
||||
if not name.startswith("_"): # Skip private/internal items
|
||||
print(f"- {name}")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import skip module: {e}")
|
||||
traceback.print_exc()
|
||||
except Exception as e:
|
||||
print(f"ERROR: An unexpected error occurred: {e}")
|
||||
traceback.print_exc()
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example script to inspect and document kicad-skip library capabilities
|
||||
"""
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
import os
|
||||
import traceback
|
||||
|
||||
def main():
|
||||
"""Examine the kicad-skip library functionality"""
|
||||
print("=== Examining kicad-skip library ===")
|
||||
|
||||
try:
|
||||
# Import the module
|
||||
print("Importing skip module...")
|
||||
import skip
|
||||
print(f"Skip module path: {skip.__file__}")
|
||||
|
||||
# Display module contents
|
||||
print("\nSkip module contents:")
|
||||
for item_name in dir(skip):
|
||||
if not item_name.startswith('_'):
|
||||
try:
|
||||
item = getattr(skip, item_name)
|
||||
if inspect.isclass(item):
|
||||
print(f" Class: {item_name}")
|
||||
# List methods of the class
|
||||
for method_name in dir(item):
|
||||
if not method_name.startswith('_'):
|
||||
method = getattr(item, method_name)
|
||||
if inspect.ismethod(method) or inspect.isfunction(method):
|
||||
print(f" - Method: {method_name}")
|
||||
elif inspect.isfunction(item):
|
||||
print(f" Function: {item_name}")
|
||||
else:
|
||||
print(f" Other: {item_name} (type: {type(item).__name__})")
|
||||
except Exception as e:
|
||||
print(f" Error examining {item_name}: {e}")
|
||||
|
||||
# Test Schematic class specifically
|
||||
print("\nExamining Schematic class:")
|
||||
if hasattr(skip, 'Schematic'):
|
||||
schematic_class = skip.Schematic
|
||||
print(f"Schematic class: {schematic_class}")
|
||||
|
||||
# Check initialization parameters
|
||||
try:
|
||||
sig = inspect.signature(schematic_class.__init__)
|
||||
print(f"Schematic.__init__ signature: {sig}")
|
||||
|
||||
# List required parameters
|
||||
required_params = [
|
||||
name for name, param in sig.parameters.items()
|
||||
if param.default == inspect.Parameter.empty and name != 'self'
|
||||
]
|
||||
print(f"Required parameters: {required_params}")
|
||||
|
||||
# Display initialization docstring
|
||||
print(f"__init__ docstring: {schematic_class.__init__.__doc__}")
|
||||
except Exception as e:
|
||||
print(f"Error examining Schematic.__init__: {e}")
|
||||
|
||||
# Create a simple test file
|
||||
test_dir = os.path.dirname(__file__)
|
||||
test_file = os.path.join(test_dir, 'test_example.kicad_sch')
|
||||
|
||||
with open(test_file, 'w') as f:
|
||||
f.write("(kicad_sch (version 20230121) (generator \"Test Example\"))\n")
|
||||
|
||||
# Try loading the test file
|
||||
print(f"\nLoading test file {test_file}")
|
||||
try:
|
||||
sch = skip.Schematic(test_file)
|
||||
print("Successfully created Schematic object")
|
||||
|
||||
# Examine the object's attributes and methods
|
||||
print("\nSchematic object attributes:")
|
||||
for attr_name in dir(sch):
|
||||
if not attr_name.startswith('_'):
|
||||
try:
|
||||
attr = getattr(sch, attr_name)
|
||||
if callable(attr):
|
||||
print(f" Method: {attr_name}")
|
||||
else:
|
||||
print(f" Attribute: {attr_name} = {attr}")
|
||||
except Exception as e:
|
||||
print(f" Error examining {attr_name}: {e}")
|
||||
|
||||
# Check for io-related methods
|
||||
print("\nLooking for IO-related methods:")
|
||||
for method_name in ['save', 'write', 'to_file', 'export', 'dump']:
|
||||
print(f" Has '{method_name}' method: {hasattr(sch, method_name)}")
|
||||
|
||||
# Examine the representation of the object
|
||||
print(f"\nStr representation: {str(sch)}")
|
||||
print(f"Repr representation: {repr(sch)}")
|
||||
|
||||
# Clean up
|
||||
os.remove(test_file)
|
||||
except Exception as e:
|
||||
print(f"Error testing Schematic: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
# Clean up even on error
|
||||
if os.path.exists(test_file):
|
||||
os.remove(test_file)
|
||||
else:
|
||||
print("Schematic class not found in skip module")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"Failed to import skip module: {e}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== Examination completed ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Manual test script for the schematic functionality
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the parent directory to the module search path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# Import our schematic modules
|
||||
from python.commands.schematic import SchematicManager
|
||||
from python.commands.component_schematic import ComponentManager
|
||||
from python.commands.connection_schematic import ConnectionManager
|
||||
|
||||
def main():
|
||||
"""Run a basic test of schematic functionality"""
|
||||
print("=== Starting manual schematic test ===")
|
||||
|
||||
# Set up test output directory
|
||||
test_dir = os.path.join(os.path.dirname(__file__), 'schematic_test_output')
|
||||
if not os.path.exists(test_dir):
|
||||
os.makedirs(test_dir)
|
||||
|
||||
# 1. Create a new schematic
|
||||
schematic_name = "TestCircuitManual"
|
||||
schematic_path = os.path.join(test_dir, f"{schematic_name}.kicad_sch")
|
||||
print(f"Creating schematic: {schematic_name}")
|
||||
|
||||
schematic = SchematicManager.create_schematic(schematic_name)
|
||||
|
||||
# 2. Add components to the schematic
|
||||
print("Adding components to schematic...")
|
||||
|
||||
# Add resistor R1
|
||||
r1_def = {
|
||||
"type": "R",
|
||||
"reference": "R1",
|
||||
"value": "10k",
|
||||
"library": "Device",
|
||||
"x": 100,
|
||||
"y": 100
|
||||
}
|
||||
r1 = ComponentManager.add_component(schematic, r1_def)
|
||||
|
||||
# Add resistor R2
|
||||
r2_def = {
|
||||
"type": "R",
|
||||
"reference": "R2",
|
||||
"value": "4.7k",
|
||||
"library": "Device",
|
||||
"x": 100,
|
||||
"y": 200
|
||||
}
|
||||
r2 = ComponentManager.add_component(schematic, r2_def)
|
||||
|
||||
# Add capacitor C1
|
||||
c1_def = {
|
||||
"type": "C",
|
||||
"reference": "C1",
|
||||
"value": "0.1uF",
|
||||
"library": "Device",
|
||||
"x": 200,
|
||||
"y": 150
|
||||
}
|
||||
c1 = ComponentManager.add_component(schematic, c1_def)
|
||||
|
||||
# 3. Add wires to connect components
|
||||
print("Adding wires to connect components...")
|
||||
|
||||
# Connect R1 to R2
|
||||
wire1 = ConnectionManager.add_wire(schematic, [150, 100], [150, 200])
|
||||
|
||||
# Connect R2 to C1
|
||||
wire2 = ConnectionManager.add_wire(schematic, [150, 200], [200, 200])
|
||||
|
||||
# Connect C1 to R1
|
||||
wire3 = ConnectionManager.add_wire(schematic, [200, 100], [150, 100])
|
||||
|
||||
# 4. Save the schematic
|
||||
print(f"Saving schematic to: {schematic_path}")
|
||||
success = SchematicManager.save_schematic(schematic, schematic_path)
|
||||
|
||||
if success:
|
||||
print(f"Successfully saved schematic to: {schematic_path}")
|
||||
else:
|
||||
print("Failed to save schematic")
|
||||
|
||||
print("=== Manual schematic test completed ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test script for the kicad-skip library functionality
|
||||
This test doesn't depend on KiCAD's Python modules like pcbnew.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
|
||||
def main():
|
||||
"""Test basic kicad-skip functionality"""
|
||||
print("=== Testing kicad-skip schematic functionality ===")
|
||||
|
||||
# Set up test output directory
|
||||
test_dir = os.path.join(os.path.dirname(__file__), 'schematic_test_output')
|
||||
if not os.path.exists(test_dir):
|
||||
os.makedirs(test_dir)
|
||||
|
||||
print("Test directory:", test_dir)
|
||||
|
||||
try:
|
||||
# Import skip module
|
||||
print("Importing skip module...")
|
||||
from skip import Schematic
|
||||
print("Successfully imported skip module")
|
||||
|
||||
# Create a new schematic
|
||||
print("Creating new schematic...")
|
||||
sch = Schematic()
|
||||
sch.version = "20230121"
|
||||
sch.generator = "KiCAD-MCP-Server-Test"
|
||||
print("Created schematic object with version:", sch.version)
|
||||
|
||||
# Add resistor component
|
||||
print("Adding resistor component...")
|
||||
resistor = sch.add_symbol(
|
||||
lib="Device",
|
||||
name="R",
|
||||
reference="R1",
|
||||
at=[100, 100],
|
||||
unit=1
|
||||
)
|
||||
resistor.property.Value.value = "10k"
|
||||
print("Added resistor:", resistor.reference, resistor.property.Value.value)
|
||||
|
||||
# Add capacitor component
|
||||
print("Adding capacitor component...")
|
||||
capacitor = sch.add_symbol(
|
||||
lib="Device",
|
||||
name="C",
|
||||
reference="C1",
|
||||
at=[200, 100],
|
||||
unit=1
|
||||
)
|
||||
capacitor.property.Value.value = "0.1uF"
|
||||
print("Added capacitor:", capacitor.reference, capacitor.property.Value.value)
|
||||
|
||||
# Add wire connection
|
||||
print("Adding wire connection...")
|
||||
wire = sch.add_wire(start=[100, 150], end=[200, 150])
|
||||
print("Added wire from", wire.start, "to", wire.end)
|
||||
|
||||
# Save the schematic
|
||||
schematic_path = os.path.join(test_dir, "skip_test.kicad_sch")
|
||||
print(f"Saving schematic to: {schematic_path}")
|
||||
sch.save(schematic_path)
|
||||
print(f"Schematic saved to: {schematic_path}")
|
||||
|
||||
# Verify the file exists
|
||||
if os.path.exists(schematic_path):
|
||||
print(f"SUCCESS: Schematic file created at: {schematic_path}")
|
||||
print(f"File size: {os.path.getsize(schematic_path)} bytes")
|
||||
else:
|
||||
print(f"ERROR: Failed to create schematic file at: {schematic_path}")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import required modules: {e}")
|
||||
traceback.print_exc()
|
||||
except Exception as e:
|
||||
print(f"ERROR: An unexpected error occurred: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("=== Test completed ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,230 +0,0 @@
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Get current file directory (ESM equivalent of __dirname)
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* Test script for the schematic generation functionality
|
||||
*
|
||||
* This script tests the KiCAD MCP server's schematic generation capabilities by:
|
||||
* 1. Creating a new schematic
|
||||
* 2. Adding components (resistors, capacitors)
|
||||
* 3. Adding connections between them
|
||||
* 4. Exporting the schematic to PDF
|
||||
*/
|
||||
|
||||
// Directory for test outputs
|
||||
const TEST_OUTPUT_DIR = path.join(__dirname, 'schematic_test_output');
|
||||
if (!fs.existsSync(TEST_OUTPUT_DIR)) {
|
||||
fs.mkdirSync(TEST_OUTPUT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Function to send a command to the KiCAD MCP server
|
||||
function sendCommand(serverProcess, command, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Create request object
|
||||
const request = {
|
||||
command,
|
||||
params
|
||||
};
|
||||
|
||||
// Convert to JSON and add newline
|
||||
const requestStr = JSON.stringify(request) + '\n';
|
||||
|
||||
// Write to stdin of server process
|
||||
console.log(`Sending request to server: ${requestStr}`);
|
||||
serverProcess.stdin.write(requestStr);
|
||||
|
||||
// Set up response handler
|
||||
let responseData = '';
|
||||
const responseHandler = (data) => {
|
||||
const chunk = data.toString();
|
||||
console.log(`Received data: ${chunk}`);
|
||||
responseData += chunk;
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
const response = JSON.parse(responseData);
|
||||
// Got a complete response
|
||||
console.log(`Parsed complete response: ${JSON.stringify(response)}`);
|
||||
serverProcess.stdout.removeListener('data', responseHandler);
|
||||
resolve(response);
|
||||
} catch (e) {
|
||||
// Not complete JSON yet, keep collecting
|
||||
console.log(`JSON parsing failed, continuing to collect data: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Set a timeout to prevent hanging indefinitely
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.log("Command timeout after 10 seconds");
|
||||
serverProcess.stdout.removeListener('data', responseHandler);
|
||||
resolve({
|
||||
success: false,
|
||||
message: "Timeout waiting for response from server"
|
||||
});
|
||||
}, 10000); // 10 second timeout
|
||||
|
||||
// Listen for response
|
||||
serverProcess.stdout.on('data', responseHandler);
|
||||
|
||||
// Handle errors
|
||||
serverProcess.stderr.on('data', (data) => {
|
||||
console.error(`Server stderr: ${data.toString()}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function runTest() {
|
||||
console.log('\n=== TESTING SCHEMATIC GENERATION ===\n');
|
||||
|
||||
// Start the KiCAD MCP server
|
||||
console.log('Starting KiCAD MCP server...');
|
||||
// Use shell: true to ensure proper command execution in Windows
|
||||
const serverProcess = spawn('node', ['dist/kicad-server.js'], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
// Log server startup messages
|
||||
serverProcess.stderr.on('data', (data) => {
|
||||
console.log(`Server startup: ${data.toString()}`);
|
||||
});
|
||||
|
||||
// Give server time to start
|
||||
console.log('Waiting for server to start...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
console.log('Server should be ready now');
|
||||
|
||||
try {
|
||||
// 1. Create a new schematic
|
||||
const schematicName = 'TestCircuit';
|
||||
const schematicPath = path.join(TEST_OUTPUT_DIR, `${schematicName}.kicad_sch`);
|
||||
console.log(`Creating schematic: ${schematicName}...`);
|
||||
|
||||
const createResult = await sendCommand(serverProcess, 'create_schematic', {
|
||||
projectName: schematicName,
|
||||
path: TEST_OUTPUT_DIR,
|
||||
metadata: {
|
||||
description: 'Test circuit schematic',
|
||||
author: 'KiCAD MCP Test'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Create schematic result:', createResult);
|
||||
|
||||
if (!createResult.success) {
|
||||
throw new Error(`Failed to create schematic: ${createResult.message}`);
|
||||
}
|
||||
|
||||
// 2. Add components: resistors, capacitor, and power/ground connections
|
||||
console.log('Adding components to schematic...');
|
||||
|
||||
// Add resistor R1
|
||||
const addR1Result = await sendCommand(serverProcess, 'add_schematic_component', {
|
||||
schematicPath: schematicPath,
|
||||
component: {
|
||||
type: 'R',
|
||||
reference: 'R1',
|
||||
value: '10k',
|
||||
library: 'Device',
|
||||
x: 100,
|
||||
y: 100
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Add R1 result:', addR1Result);
|
||||
|
||||
// Add resistor R2
|
||||
const addR2Result = await sendCommand(serverProcess, 'add_schematic_component', {
|
||||
schematicPath: schematicPath,
|
||||
component: {
|
||||
type: 'R',
|
||||
reference: 'R2',
|
||||
value: '4.7k',
|
||||
library: 'Device',
|
||||
x: 100,
|
||||
y: 200
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Add R2 result:', addR2Result);
|
||||
|
||||
// Add capacitor C1
|
||||
const addC1Result = await sendCommand(serverProcess, 'add_schematic_component', {
|
||||
schematicPath: schematicPath,
|
||||
component: {
|
||||
type: 'C',
|
||||
reference: 'C1',
|
||||
value: '0.1uF',
|
||||
library: 'Device',
|
||||
x: 200,
|
||||
y: 150
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Add C1 result:', addC1Result);
|
||||
|
||||
// 3. Add wires to connect components
|
||||
console.log('Adding wires to connect components...');
|
||||
|
||||
// Connect R1 to R2
|
||||
const addWire1Result = await sendCommand(serverProcess, 'add_schematic_wire', {
|
||||
schematicPath: schematicPath,
|
||||
startPoint: [150, 100],
|
||||
endPoint: [150, 200]
|
||||
});
|
||||
|
||||
console.log('Add wire 1 result:', addWire1Result);
|
||||
|
||||
// Connect R2 to C1
|
||||
const addWire2Result = await sendCommand(serverProcess, 'add_schematic_wire', {
|
||||
schematicPath: schematicPath,
|
||||
startPoint: [150, 200],
|
||||
endPoint: [200, 200]
|
||||
});
|
||||
|
||||
console.log('Add wire 2 result:', addWire2Result);
|
||||
|
||||
// Connect C1 to R1
|
||||
const addWire3Result = await sendCommand(serverProcess, 'add_schematic_wire', {
|
||||
schematicPath: schematicPath,
|
||||
startPoint: [200, 100],
|
||||
endPoint: [150, 100]
|
||||
});
|
||||
|
||||
console.log('Add wire 3 result:', addWire3Result);
|
||||
|
||||
// 4. Export to PDF
|
||||
console.log('Exporting schematic to PDF...');
|
||||
const pdfPath = path.join(TEST_OUTPUT_DIR, `${schematicName}.pdf`);
|
||||
|
||||
const exportResult = await sendCommand(serverProcess, 'export_schematic_pdf', {
|
||||
schematicPath: schematicPath,
|
||||
outputPath: pdfPath
|
||||
});
|
||||
|
||||
console.log('Export PDF result:', exportResult);
|
||||
|
||||
if (exportResult.success) {
|
||||
console.log(`PDF successfully created at: ${pdfPath}`);
|
||||
} else {
|
||||
console.log(`Failed to create PDF: ${exportResult.message}`);
|
||||
}
|
||||
|
||||
console.log('\n=== SCHEMATIC GENERATION TEST COMPLETED ===\n');
|
||||
console.log(`Schematic file: ${schematicPath}`);
|
||||
console.log(`PDF file: ${pdfPath}`);
|
||||
} catch (error) {
|
||||
console.error('Test error:', error);
|
||||
} finally {
|
||||
// Kill the server process
|
||||
serverProcess.kill();
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
runTest().catch(console.error);
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debug test script for the schematic manager implementation
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
|
||||
# Add the parent directory to the module search path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
def main():
|
||||
"""Test the SchematicManager functions with detailed debug output"""
|
||||
print("=== DEBUGGING SchematicManager functionality ===")
|
||||
|
||||
# Set up test output directory
|
||||
test_dir = os.path.join(os.path.dirname(__file__), 'schematic_test_output')
|
||||
if not os.path.exists(test_dir):
|
||||
os.makedirs(test_dir)
|
||||
|
||||
print("Test directory:", test_dir)
|
||||
|
||||
try:
|
||||
# Import directly from skip for comparison
|
||||
print("Importing skip module...")
|
||||
from skip import Schematic
|
||||
print("Successfully imported skip module")
|
||||
|
||||
# Create a template file directly
|
||||
template_path = os.path.join(test_dir, "debug_template.kicad_sch")
|
||||
print(f"Creating template file at: {template_path}")
|
||||
with open(template_path, 'w') as f:
|
||||
f.write("(kicad_sch (version 20230121) (generator \"KiCAD-MCP-Server-Debug\"))\n")
|
||||
|
||||
print(f"Template file created, size: {os.path.getsize(template_path)} bytes")
|
||||
|
||||
# Load the template with skip directly
|
||||
print("Loading template with skip.Schematic...")
|
||||
try:
|
||||
sch = Schematic(template_path)
|
||||
print("Successfully loaded template")
|
||||
|
||||
# Save directly
|
||||
output_path = os.path.join(test_dir, "direct_save.kicad_sch")
|
||||
print(f"Saving with skip.Schematic.save() to: {output_path}")
|
||||
sch.save(output_path)
|
||||
|
||||
if os.path.exists(output_path):
|
||||
print(f"Direct save successful, file size: {os.path.getsize(output_path)} bytes")
|
||||
else:
|
||||
print("Direct save failed, no file created")
|
||||
except Exception as e:
|
||||
print(f"Error using skip directly: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n--- Now testing SchematicManager ---\n")
|
||||
|
||||
# Import our SchematicManager
|
||||
print("Importing SchematicManager...")
|
||||
from python.commands.schematic import SchematicManager
|
||||
print("Successfully imported SchematicManager")
|
||||
|
||||
# Create a new schematic
|
||||
print("\nCreating new schematic with SchematicManager...")
|
||||
schematic_name = "TestDebugManager"
|
||||
metadata = {
|
||||
"description": "Debug test schematic",
|
||||
"author": "Debug script"
|
||||
}
|
||||
|
||||
try:
|
||||
sch = SchematicManager.create_schematic(schematic_name, metadata)
|
||||
print("Successfully created schematic object")
|
||||
|
||||
# Print schematic properties
|
||||
print("Schematic properties:")
|
||||
print(f" Version: {sch.version}")
|
||||
print(f" Generator: {sch.generator}")
|
||||
|
||||
# Save the schematic
|
||||
schematic_path = os.path.join(test_dir, f"{schematic_name}.kicad_sch")
|
||||
print(f"\nSaving schematic to: {schematic_path}")
|
||||
|
||||
try:
|
||||
success = SchematicManager.save_schematic(sch, schematic_path)
|
||||
|
||||
if success:
|
||||
print(f"Successfully saved schematic to: {schematic_path}")
|
||||
print(f"File size: {os.path.getsize(schematic_path)} bytes")
|
||||
else:
|
||||
print("SchematicManager.save_schematic returned False")
|
||||
except Exception as e:
|
||||
print(f"Error in save_schematic: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in create_schematic: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import required modules: {e}")
|
||||
traceback.print_exc()
|
||||
except Exception as e:
|
||||
print(f"ERROR: An unexpected error occurred: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== Debug test completed ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the schematic manager implementation using KiCAD python
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
|
||||
# Add the parent directory to the module search path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
def main():
|
||||
"""Test the SchematicManager functions"""
|
||||
print("=== Testing SchematicManager functionality ===")
|
||||
|
||||
try:
|
||||
# Set up test output directory
|
||||
test_dir = os.path.join(os.path.dirname(__file__), 'schematic_test_output')
|
||||
if not os.path.exists(test_dir):
|
||||
os.makedirs(test_dir)
|
||||
|
||||
print("Test directory:", test_dir)
|
||||
|
||||
# Import our SchematicManager
|
||||
print("Importing SchematicManager...")
|
||||
from python.commands.schematic import SchematicManager
|
||||
print("Successfully imported SchematicManager")
|
||||
|
||||
# Create a new schematic
|
||||
print("\nCreating new schematic...")
|
||||
schematic_name = "TestSchemManager"
|
||||
metadata = {
|
||||
"description": "Test schematic",
|
||||
"author": "Test script"
|
||||
}
|
||||
sch = SchematicManager.create_schematic(schematic_name, metadata)
|
||||
print("Successfully created schematic object")
|
||||
|
||||
# Save the schematic
|
||||
schematic_path = os.path.join(test_dir, f"{schematic_name}.kicad_sch")
|
||||
print(f"\nSaving schematic to: {schematic_path}")
|
||||
success = SchematicManager.save_schematic(sch, schematic_path)
|
||||
|
||||
if success:
|
||||
print(f"Successfully saved schematic to: {schematic_path}")
|
||||
else:
|
||||
print("Failed to save schematic")
|
||||
return
|
||||
|
||||
# Load the schematic
|
||||
print("\nLoading schematic from file...")
|
||||
loaded_sch = SchematicManager.load_schematic(schematic_path)
|
||||
|
||||
if loaded_sch:
|
||||
print("Successfully loaded schematic")
|
||||
|
||||
# Get metadata
|
||||
print("\nGetting schematic metadata...")
|
||||
metadata = SchematicManager.get_schematic_metadata(loaded_sch)
|
||||
print(f"Metadata: {metadata}")
|
||||
else:
|
||||
print("Failed to load schematic")
|
||||
|
||||
# Verify the file exists
|
||||
if os.path.exists(schematic_path):
|
||||
print(f"\nSCHEMATIC TEST SUCCESSFUL: File created at: {schematic_path}")
|
||||
print(f"File size: {os.path.getsize(schematic_path)} bytes")
|
||||
else:
|
||||
print(f"\nERROR: File not found at: {schematic_path}")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import required modules: {e}")
|
||||
traceback.print_exc()
|
||||
except Exception as e:
|
||||
print(f"ERROR: An unexpected error occurred: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== Test completed ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user