kicad-sch-api 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kicad-sch-api might be problematic. Click here for more details.

kicad_sch_api/__init__.py CHANGED
@@ -42,7 +42,7 @@ Advanced Usage:
42
42
  print(f"Found {len(issues)} validation issues")
43
43
  """
44
44
 
45
- __version__ = "0.0.1"
45
+ __version__ = "0.0.2"
46
46
  __author__ = "Circuit-Synth"
47
47
  __email__ = "info@circuit-synth.com"
48
48
 
@@ -54,7 +54,7 @@ from .library.cache import SymbolLibraryCache, get_symbol_cache
54
54
  from .utils.validation import ValidationError, ValidationIssue
55
55
 
56
56
  # Version info
57
- VERSION_INFO = (0, 0, 1)
57
+ VERSION_INFO = (0, 0, 2)
58
58
 
59
59
  # Public API
60
60
  __all__ = [
@@ -471,9 +471,13 @@ class ComponentCollection:
471
471
  matching = self.filter(**criteria)
472
472
 
473
473
  for component in matching:
474
- # Update basic properties
474
+ # Update basic properties and handle special cases
475
475
  for key, value in updates.items():
476
- if hasattr(component, key):
476
+ if key == 'properties' and isinstance(value, dict):
477
+ # Handle properties dictionary specially
478
+ for prop_name, prop_value in value.items():
479
+ component.set_property(prop_name, str(prop_value))
480
+ elif hasattr(component, key) and key not in ['properties']:
477
481
  setattr(component, key, value)
478
482
  else:
479
483
  # Add as custom property
@@ -200,7 +200,7 @@ class SExpressionParser:
200
200
  element_type = str(item[0]) if isinstance(item[0], sexpdata.Symbol) else None
201
201
 
202
202
  if element_type == "version":
203
- schematic_data["version"] = item[1] if len(item) > 1 else None
203
+ schematic_data["version"] = str(item[1]) if len(item) > 1 else None
204
204
  elif element_type == "generator":
205
205
  schematic_data["generator"] = item[1] if len(item) > 1 else None
206
206
  elif element_type == "uuid":
@@ -244,9 +244,9 @@ class SExpressionParser:
244
244
  if schematic_data.get("title_block"):
245
245
  sexp_data.append(self._title_block_to_sexp(schematic_data["title_block"]))
246
246
 
247
- # Add lib_symbols
248
- if schematic_data.get("lib_symbols"):
249
- sexp_data.append(self._lib_symbols_to_sexp(schematic_data["lib_symbols"]))
247
+ # Add lib_symbols (always include for KiCAD compatibility)
248
+ lib_symbols = schematic_data.get("lib_symbols", {})
249
+ sexp_data.append(self._lib_symbols_to_sexp(lib_symbols))
250
250
 
251
251
  # Add components
252
252
  for component in schematic_data.get("components", []):
@@ -264,6 +264,11 @@ class SExpressionParser:
264
264
  for label in schematic_data.get("labels", []):
265
265
  sexp_data.append(self._label_to_sexp(label))
266
266
 
267
+ # Add symbol_instances (required by KiCAD)
268
+ symbol_instances = schematic_data.get("symbol_instances", [])
269
+ if symbol_instances or schematic_data.get("components"):
270
+ sexp_data.append([sexpdata.Symbol("symbol_instances")])
271
+
267
272
  return sexp_data
268
273
 
269
274
  def _parse_title_block(self, item: List[Any]) -> Dict[str, Any]:
@@ -321,7 +326,11 @@ class SExpressionParser:
321
326
  elif prop_name == "Footprint":
322
327
  symbol_data["footprint"] = prop_data.get("value")
323
328
  else:
324
- symbol_data["properties"][prop_name] = prop_data.get("value")
329
+ # Unescape quotes in property values when loading
330
+ prop_value = prop_data.get("value")
331
+ if prop_value:
332
+ prop_value = str(prop_value).replace('\\"', '"')
333
+ symbol_data["properties"][prop_name] = prop_value
325
334
  elif element_type == "in_bom":
326
335
  symbol_data["in_bom"] = sub_item[1] == "yes" if len(sub_item) > 1 else True
327
336
  elif element_type == "on_board":
@@ -379,13 +388,15 @@ class SExpressionParser:
379
388
  if symbol_data.get("lib_id"):
380
389
  sexp.append([sexpdata.Symbol("lib_id"), symbol_data["lib_id"]])
381
390
 
382
- # Add position and rotation
391
+ # Add position and rotation (preserve original format)
383
392
  pos = symbol_data.get("position", Point(0, 0))
384
393
  rotation = symbol_data.get("rotation", 0)
385
- if rotation != 0:
386
- sexp.append([sexpdata.Symbol("at"), pos.x, pos.y, rotation])
387
- else:
388
- sexp.append([sexpdata.Symbol("at"), pos.x, pos.y])
394
+ # Format numbers as integers if they are whole numbers
395
+ x = int(pos.x) if pos.x == int(pos.x) else pos.x
396
+ y = int(pos.y) if pos.y == int(pos.y) else pos.y
397
+ r = int(rotation) if rotation == int(rotation) else rotation
398
+ # Always include rotation for format consistency with KiCAD
399
+ sexp.append([sexpdata.Symbol("at"), x, y, r])
389
400
 
390
401
  if symbol_data.get("uuid"):
391
402
  sexp.append([sexpdata.Symbol("uuid"), symbol_data["uuid"]])
@@ -395,11 +406,14 @@ class SExpressionParser:
395
406
  sexp.append([sexpdata.Symbol("property"), "Reference", symbol_data["reference"]])
396
407
  if symbol_data.get("value"):
397
408
  sexp.append([sexpdata.Symbol("property"), "Value", symbol_data["value"]])
398
- if symbol_data.get("footprint"):
399
- sexp.append([sexpdata.Symbol("property"), "Footprint", symbol_data["footprint"]])
409
+ footprint = symbol_data.get("footprint")
410
+ if footprint is not None: # Include empty strings but not None
411
+ sexp.append([sexpdata.Symbol("property"), "Footprint", footprint])
400
412
 
401
413
  for prop_name, prop_value in symbol_data.get("properties", {}).items():
402
- sexp.append([sexpdata.Symbol("property"), prop_name, prop_value])
414
+ # Escape quotes in property values for proper S-expression format
415
+ escaped_value = str(prop_value).replace('"', '\\"')
416
+ sexp.append([sexpdata.Symbol("property"), prop_name, escaped_value])
403
417
 
404
418
  # Add BOM and board settings
405
419
  sexp.append([sexpdata.Symbol("in_bom"), "yes" if symbol_data.get("in_bom", True) else "no"])
@@ -426,8 +440,16 @@ class SExpressionParser:
426
440
 
427
441
  def _lib_symbols_to_sexp(self, lib_symbols: Dict[str, Any]) -> List[Any]:
428
442
  """Convert lib_symbols to S-expression."""
429
- # Implementation for lib_symbols conversion
430
- return [sexpdata.Symbol("lib_symbols")]
443
+ sexp = [sexpdata.Symbol("lib_symbols")]
444
+
445
+ # Add each symbol definition
446
+ for symbol_name, symbol_def in lib_symbols.items():
447
+ if isinstance(symbol_def, dict):
448
+ symbol_sexp = [sexpdata.Symbol("symbol"), symbol_name]
449
+ # Add symbol definition details (for now, basic structure)
450
+ sexp.append(symbol_sexp)
451
+
452
+ return sexp
431
453
 
432
454
  def get_validation_issues(self) -> List[ValidationIssue]:
433
455
  """Get list of validation issues from last parse operation."""
@@ -87,7 +87,7 @@ class SchematicValidator:
87
87
  """
88
88
  self.strict = strict
89
89
  self.issues = []
90
- self._valid_reference_pattern = re.compile(r"^[A-Z]+[0-9]*$")
90
+ self._valid_reference_pattern = re.compile(r"^(#[A-Z]+[0-9]+|[A-Z]+[0-9]*)$")
91
91
  self._valid_lib_id_pattern = re.compile(r"^[^:]+:[^:]+$")
92
92
 
93
93
  def validate_schematic_data(self, schematic_data: Dict[str, Any]) -> List[ValidationIssue]:
@@ -133,7 +133,7 @@ class SchematicValidator:
133
133
  Validate component reference format.
134
134
 
135
135
  Args:
136
- reference: Reference to validate (e.g., "R1", "U5")
136
+ reference: Reference to validate (e.g., "R1", "U5", "#PWR01")
137
137
 
138
138
  Returns:
139
139
  True if reference is valid
@@ -231,7 +231,7 @@ class SchematicValidator:
231
231
  message=f"{context}: Invalid reference format: {reference}",
232
232
  level=ValidationLevel.ERROR,
233
233
  context={"reference": reference},
234
- suggestion="Reference should match pattern: [A-Z]+[0-9]*",
234
+ suggestion="Reference should match pattern: [A-Z]+[0-9]* or #[A-Z]+[0-9]* (for power symbols)",
235
235
  )
236
236
  )
237
237
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kicad-sch-api
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Professional KiCAD schematic manipulation library with exact format preservation and AI agent integration
5
5
  Author-email: Circuit-Synth <info@circuit-synth.com>
6
6
  Maintainer-email: Circuit-Synth <info@circuit-synth.com>
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://circuit-synth.github.io/kicad-sch-api/
10
10
  Project-URL: Repository, https://github.com/circuit-synth/kicad-sch-api.git
11
11
  Project-URL: Bug Reports, https://github.com/circuit-synth/kicad-sch-api/issues
12
12
  Project-URL: Changelog, https://github.com/circuit-synth/kicad-sch-api/blob/main/CHANGELOG.md
13
- Keywords: kicad,schematic,eda,electronics,circuit-design,ai,mcp,automation,pcb
13
+ Keywords: kicad,schematic,eda,electronics,circuit-design,ai,automation,pcb
14
14
  Classifier: Development Status :: 4 - Beta
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: Intended Audience :: Science/Research
@@ -28,8 +28,6 @@ Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
29
  Requires-Dist: sexpdata>=0.0.3
30
30
  Requires-Dist: typing-extensions>=4.0.0; python_version < "3.11"
31
- Provides-Extra: mcp
32
- Requires-Dist: mcp>=0.1.0; extra == "mcp"
33
31
  Provides-Extra: dev
34
32
  Requires-Dist: pytest>=7.0.0; extra == "dev"
35
33
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -49,13 +47,11 @@ Dynamic: license-file
49
47
 
50
48
  **Professional KiCAD Schematic Manipulation Library with AI Agent Integration**
51
49
 
52
- A modern, high-performance Python library for programmatic manipulation of KiCAD schematic files (.kicad_sch) with exact format preservation, enhanced component management, and native AI agent support via Model Context Protocol (MCP).
53
50
 
54
51
  ## 🚀 Key Features
55
52
 
56
53
  - **📋 Exact Format Preservation**: Output matches KiCAD's native formatting exactly
57
54
  - **⚡ High Performance**: Optimized for large schematics with symbol caching
58
- - **🤖 AI Agent Integration**: Native MCP server for seamless AI agent interaction
59
55
  - **🔧 Enhanced API**: Intuitive object-oriented interface with bulk operations
60
56
  - **📚 Advanced Library Management**: Multi-source symbol lookup and caching
61
57
  - **✅ Professional Validation**: Comprehensive error collection and reporting
@@ -68,7 +64,6 @@ A modern, high-performance Python library for programmatic manipulation of KiCAD
68
64
  | **Schematic Support** | ✅ Full | ✅ Full | ❌ PCB Only |
69
65
  | **Format Preservation** | ✅ Exact | ❌ Basic | N/A |
70
66
  | **Performance** | ✅ Optimized | ⚠️ Basic | N/A |
71
- | **AI Integration** | ✅ Native MCP | ❌ None | ❌ None |
72
67
  | **Library Management** | ✅ Advanced | ⚠️ Basic | N/A |
73
68
  | **Runtime Dependencies** | ❌ None | ❌ None | ✅ KiCAD Required |
74
69
 
@@ -83,8 +78,6 @@ git clone https://github.com/circuit-synth/kicad-sch-api.git
83
78
  cd kicad-sch-api/python
84
79
  pip install -e .
85
80
 
86
- # For AI agent integration (MCP server)
87
- cd ../mcp-server
88
81
  npm install
89
82
  npm run build
90
83
  ```
@@ -141,15 +134,12 @@ stats = sch.get_performance_stats()
141
134
  print(f"Cache hit rate: {stats['symbol_cache']['hit_rate_percent']}%")
142
135
  ```
143
136
 
144
- ### AI Agent Integration (MCP)
145
137
 
146
- Configure in Claude Desktop or compatible MCP client:
147
138
 
148
139
  ```json
149
140
  {
150
141
  "kicad-sch": {
151
142
  "command": "node",
152
- "args": ["/path/to/kicad-sch-api/mcp-server/dist/index.js"],
153
143
  "env": {
154
144
  "PYTHON_PATH": "python3",
155
145
  "KICAD_SCH_API_PATH": "/path/to/kicad-sch-api/python"
@@ -165,7 +155,6 @@ User: "Create a voltage divider circuit with two 10k resistors"
165
155
 
166
156
  Claude: I'll create a voltage divider circuit for you.
167
157
 
168
- [Agent automatically uses MCP tools to:]
169
158
  1. Create new schematic
170
159
  2. Add R1 (10k resistor) at (100, 100)
171
160
  3. Add R2 (10k resistor) at (100, 150)
@@ -187,8 +176,6 @@ The library consists of two main components:
187
176
  - **Symbol Caching**: High-performance library symbol management
188
177
  - **Comprehensive Validation**: Error collection and professional reporting
189
178
 
190
- ### MCP Server (AI Integration)
191
- - **TypeScript MCP Server**: Implements Anthropic's MCP specification
192
179
  - **Python Bridge**: Reliable subprocess communication
193
180
  - **Comprehensive Tools**: 15+ tools for complete schematic manipulation
194
181
  - **Professional Error Handling**: Detailed error context for AI agents
@@ -200,8 +187,6 @@ The library consists of two main components:
200
187
  cd python
201
188
  python -m pytest tests/ -v --cov=kicad_sch_api
202
189
 
203
- # MCP server tests
204
- cd mcp-server
205
190
  npm test
206
191
 
207
192
  # Format preservation tests
@@ -0,0 +1,18 @@
1
+ kicad_sch_api/__init__.py,sha256=mogTeOic6O-WWOfpoRuBzPicVNha-gbdMrhJX4ir5PY,2821
2
+ kicad_sch_api/py.typed,sha256=e4ldqxwpY7pNDG1olbvj4HSKr8sZ9vxgA_2ek8xXn-Q,70
3
+ kicad_sch_api/core/__init__.py,sha256=ur_KeYBlGKl-e1hLpLdxAhGV2A-PCCGkcqd0r6KSeBA,566
4
+ kicad_sch_api/core/components.py,sha256=P_-qZPNpA8LAT0M3dLHFF6XSFyNAnSlcKjUf6aREdUo,22425
5
+ kicad_sch_api/core/formatter.py,sha256=MVF3Hhc5ZVPyVDYnGcxb88q0x0UTr2DQa45gppiFqNk,11953
6
+ kicad_sch_api/core/parser.py,sha256=Ao1j8JPWHGBkznUi9k-RO4_sLcCrkE_5ialSxZAd0cQ,17943
7
+ kicad_sch_api/core/schematic.py,sha256=O76nZvj4qffHkFrMJV5Z35xU95efPW-_mtAD8Nni7ao,15553
8
+ kicad_sch_api/core/types.py,sha256=VyzloTl4RbjMKj0TKu5rEZ-rtxtiT8nvQw8L6xawEvs,9980
9
+ kicad_sch_api/library/__init__.py,sha256=NG9UTdcpn25Bl9tPsYs9ED7bvpaVPVdtLMbnxkQkOnU,250
10
+ kicad_sch_api/library/cache.py,sha256=_JtzEGgO7ViIKF4W2zVrvmHQBIiosp9hOr9pG06Tw6I,18917
11
+ kicad_sch_api/utils/__init__.py,sha256=1V_yGgI7jro6MUc4Pviux_WIeJ1wmiYFID186SZwWLQ,277
12
+ kicad_sch_api/utils/validation.py,sha256=7QZBtPHKu24SA6Bhkj7M-rxs76AVQZWhISDfsJYfV_0,15620
13
+ kicad_sch_api-0.0.2.dist-info/licenses/LICENSE,sha256=Em65Nvte1G9MHc0rHqtYuGkCPcshD588itTa358J6gs,1070
14
+ kicad_sch_api-0.0.2.dist-info/METADATA,sha256=0wa5C_1GHCirtAdlUTh2Eqqa1AreZkeTts6WkQaNjd8,6810
15
+ kicad_sch_api-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ kicad_sch_api-0.0.2.dist-info/entry_points.txt,sha256=VWKsFi2Jv7G_tmio3cNVhhIBfv_OZFaKa-T_ED84lc8,57
17
+ kicad_sch_api-0.0.2.dist-info/top_level.txt,sha256=n0ex4gOJ1b_fARowcGqRzyOGZcHRhc5LZa6_vVgGxcI,14
18
+ kicad_sch_api-0.0.2.dist-info/RECORD,,
@@ -1,5 +0,0 @@
1
- """MCP (Model Context Protocol) integration for kicad-sch-api."""
2
-
3
- from .server import MCPInterface
4
-
5
- __all__ = ["MCPInterface"]
@@ -1,500 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Python MCP interface for kicad-sch-api.
4
-
5
- This script provides the Python side of the MCP bridge, handling commands
6
- from the TypeScript MCP server and executing them using kicad-sch-api.
7
- """
8
-
9
- import json
10
- import logging
11
- import sys
12
- import traceback
13
- from typing import Any, Dict, Optional
14
-
15
- # Configure logging
16
- logging.basicConfig(
17
- level=logging.INFO,
18
- format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
19
- handlers=[logging.StreamHandler(sys.stderr)],
20
- )
21
-
22
- logger = logging.getLogger("kicad_sch_api.mcp")
23
-
24
- # Import kicad-sch-api components
25
- try:
26
- from ..core.components import Component
27
- from ..core.schematic import Schematic
28
- from ..library.cache import get_symbol_cache
29
- from ..utils.validation import ValidationError, ValidationIssue
30
- except ImportError as e:
31
- logger.error(f"Failed to import kicad-sch-api modules: {e}")
32
- sys.exit(1)
33
-
34
-
35
- class MCPInterface:
36
- """MCP command interface for kicad-sch-api."""
37
-
38
- def __init__(self):
39
- """Initialize the MCP interface."""
40
- self.current_schematic: Optional[Schematic] = None
41
- self.symbol_cache = get_symbol_cache()
42
-
43
- # Command handlers
44
- self.handlers = {
45
- "ping": self.ping,
46
- "load_schematic": self.load_schematic,
47
- "save_schematic": self.save_schematic,
48
- "create_schematic": self.create_schematic,
49
- "add_component": self.add_component,
50
- "update_component": self.update_component,
51
- "remove_component": self.remove_component,
52
- "get_component": self.get_component,
53
- "find_components": self.find_components,
54
- "add_wire": self.add_wire,
55
- "connect_components": self.connect_components,
56
- "bulk_update_components": self.bulk_update_components,
57
- "validate_schematic": self.validate_schematic,
58
- "get_schematic_summary": self.get_schematic_summary,
59
- "search_library_symbols": self.search_library_symbols,
60
- "add_library_path": self.add_library_path,
61
- }
62
-
63
- logger.info("MCP interface initialized")
64
-
65
- def ping(self, params: Dict[str, Any]) -> Dict[str, Any]:
66
- """Health check command."""
67
- return {
68
- "success": True,
69
- "message": "kicad-sch-api MCP interface is ready",
70
- "version": "0.0.1",
71
- }
72
-
73
- def load_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
74
- """Load a schematic file."""
75
- try:
76
- file_path = params.get("file_path")
77
- if not file_path:
78
- return {"success": False, "error": "file_path parameter required"}
79
-
80
- self.current_schematic = Schematic.load(file_path)
81
- summary = self.current_schematic.get_summary()
82
-
83
- return {
84
- "success": True,
85
- "message": f"Loaded schematic: {file_path}",
86
- "summary": summary,
87
- }
88
- except Exception as e:
89
- logger.error(f"Error loading schematic: {e}")
90
- return {"success": False, "error": str(e)}
91
-
92
- def save_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
93
- """Save the current schematic."""
94
- try:
95
- if not self.current_schematic:
96
- return {"success": False, "error": "No schematic loaded"}
97
-
98
- file_path = params.get("file_path")
99
- preserve_format = params.get("preserve_format", True)
100
-
101
- self.current_schematic.save(file_path, preserve_format)
102
-
103
- return {
104
- "success": True,
105
- "message": f"Saved schematic to: {self.current_schematic.file_path}",
106
- }
107
- except Exception as e:
108
- logger.error(f"Error saving schematic: {e}")
109
- return {"success": False, "error": str(e)}
110
-
111
- def create_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
112
- """Create a new schematic."""
113
- try:
114
- name = params.get("name", "New Circuit")
115
- self.current_schematic = Schematic.create(name)
116
-
117
- return {
118
- "success": True,
119
- "message": f"Created new schematic: {name}",
120
- "summary": self.current_schematic.get_summary(),
121
- }
122
- except Exception as e:
123
- logger.error(f"Error creating schematic: {e}")
124
- return {"success": False, "error": str(e)}
125
-
126
- def add_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
127
- """Add a component to the schematic."""
128
- try:
129
- if not self.current_schematic:
130
- return {"success": False, "error": "No schematic loaded"}
131
-
132
- lib_id = params.get("lib_id")
133
- if not lib_id:
134
- return {"success": False, "error": "lib_id parameter required"}
135
-
136
- # Extract parameters
137
- reference = params.get("reference")
138
- value = params.get("value", "")
139
- position = params.get("position")
140
- footprint = params.get("footprint")
141
- properties = params.get("properties", {})
142
-
143
- # Convert position if provided
144
- pos_tuple = None
145
- if position:
146
- pos_tuple = (position["x"], position["y"])
147
-
148
- # Add component
149
- component = self.current_schematic.components.add(
150
- lib_id=lib_id,
151
- reference=reference,
152
- value=value,
153
- position=pos_tuple,
154
- footprint=footprint,
155
- **properties,
156
- )
157
-
158
- return {
159
- "success": True,
160
- "message": f"Added component: {component.reference}",
161
- "component": component.to_dict(),
162
- }
163
- except Exception as e:
164
- logger.error(f"Error adding component: {e}")
165
- return {"success": False, "error": str(e)}
166
-
167
- def update_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
168
- """Update a component's properties."""
169
- try:
170
- if not self.current_schematic:
171
- return {"success": False, "error": "No schematic loaded"}
172
-
173
- reference = params.get("reference")
174
- if not reference:
175
- return {"success": False, "error": "reference parameter required"}
176
-
177
- component = self.current_schematic.components.get(reference)
178
- if not component:
179
- return {"success": False, "error": f"Component not found: {reference}"}
180
-
181
- # Apply updates
182
- updates = 0
183
- if "value" in params:
184
- component.value = params["value"]
185
- updates += 1
186
-
187
- if "position" in params:
188
- pos = params["position"]
189
- component.position = (pos["x"], pos["y"])
190
- updates += 1
191
-
192
- if "footprint" in params:
193
- component.footprint = params["footprint"]
194
- updates += 1
195
-
196
- if "properties" in params:
197
- for name, value in params["properties"].items():
198
- component.set_property(name, value)
199
- updates += 1
200
-
201
- return {
202
- "success": True,
203
- "message": f"Updated component {reference} ({updates} changes)",
204
- "component": component.to_dict(),
205
- }
206
- except Exception as e:
207
- logger.error(f"Error updating component: {e}")
208
- return {"success": False, "error": str(e)}
209
-
210
- def remove_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
211
- """Remove a component from the schematic."""
212
- try:
213
- if not self.current_schematic:
214
- return {"success": False, "error": "No schematic loaded"}
215
-
216
- reference = params.get("reference")
217
- if not reference:
218
- return {"success": False, "error": "reference parameter required"}
219
-
220
- success = self.current_schematic.components.remove(reference)
221
-
222
- if success:
223
- return {"success": True, "message": f"Removed component: {reference}"}
224
- else:
225
- return {"success": False, "error": f"Component not found: {reference}"}
226
- except Exception as e:
227
- logger.error(f"Error removing component: {e}")
228
- return {"success": False, "error": str(e)}
229
-
230
- def get_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
231
- """Get detailed information about a component."""
232
- try:
233
- if not self.current_schematic:
234
- return {"success": False, "error": "No schematic loaded"}
235
-
236
- reference = params.get("reference")
237
- if not reference:
238
- return {"success": False, "error": "reference parameter required"}
239
-
240
- component = self.current_schematic.components.get(reference)
241
- if not component:
242
- return {"success": False, "error": f"Component not found: {reference}"}
243
-
244
- return {"success": True, "component": component.to_dict()}
245
- except Exception as e:
246
- logger.error(f"Error getting component: {e}")
247
- return {"success": False, "error": str(e)}
248
-
249
- def find_components(self, params: Dict[str, Any]) -> Dict[str, Any]:
250
- """Find components by criteria."""
251
- try:
252
- if not self.current_schematic:
253
- return {"success": False, "error": "No schematic loaded"}
254
-
255
- # Filter out None values and convert to filter criteria
256
- criteria = {k: v for k, v in params.items() if v is not None}
257
-
258
- # Special handling for in_area
259
- if "in_area" in criteria:
260
- area = criteria["in_area"]
261
- if len(area) == 4:
262
- criteria["in_area"] = tuple(area)
263
-
264
- components = self.current_schematic.components.filter(**criteria)
265
-
266
- return {
267
- "success": True,
268
- "count": len(components),
269
- "components": [comp.to_dict() for comp in components],
270
- }
271
- except Exception as e:
272
- logger.error(f"Error finding components: {e}")
273
- return {"success": False, "error": str(e)}
274
-
275
- def add_wire(self, params: Dict[str, Any]) -> Dict[str, Any]:
276
- """Add a wire connection."""
277
- try:
278
- if not self.current_schematic:
279
- return {"success": False, "error": "No schematic loaded"}
280
-
281
- start = params.get("start")
282
- end = params.get("end")
283
-
284
- if not start or not end:
285
- return {"success": False, "error": "start and end parameters required"}
286
-
287
- wire_uuid = self.current_schematic.add_wire(
288
- (start["x"], start["y"]), (end["x"], end["y"])
289
- )
290
-
291
- return {
292
- "success": True,
293
- "message": f"Added wire from {start} to {end}",
294
- "wire_uuid": wire_uuid,
295
- }
296
- except Exception as e:
297
- logger.error(f"Error adding wire: {e}")
298
- return {"success": False, "error": str(e)}
299
-
300
- def connect_components(self, params: Dict[str, Any]) -> Dict[str, Any]:
301
- """Connect two component pins with a wire."""
302
- try:
303
- if not self.current_schematic:
304
- return {"success": False, "error": "No schematic loaded"}
305
-
306
- from_comp = params.get("from_component")
307
- from_pin = params.get("from_pin")
308
- to_comp = params.get("to_component")
309
- to_pin = params.get("to_pin")
310
-
311
- if not all([from_comp, from_pin, to_comp, to_pin]):
312
- return {"success": False, "error": "All connection parameters required"}
313
-
314
- # Get component pin positions
315
- comp1 = self.current_schematic.components.get(from_comp)
316
- comp2 = self.current_schematic.components.get(to_comp)
317
-
318
- if not comp1:
319
- return {"success": False, "error": f"Component not found: {from_comp}"}
320
- if not comp2:
321
- return {"success": False, "error": f"Component not found: {to_comp}"}
322
-
323
- pin1_pos = comp1.get_pin_position(from_pin)
324
- pin2_pos = comp2.get_pin_position(to_pin)
325
-
326
- if not pin1_pos or not pin2_pos:
327
- return {"success": False, "error": "Could not determine pin positions"}
328
-
329
- # Add wire between pins
330
- wire_uuid = self.current_schematic.add_wire(pin1_pos, pin2_pos)
331
-
332
- return {
333
- "success": True,
334
- "message": f"Connected {from_comp}.{from_pin} to {to_comp}.{to_pin}",
335
- "wire_uuid": wire_uuid,
336
- }
337
- except Exception as e:
338
- logger.error(f"Error connecting components: {e}")
339
- return {"success": False, "error": str(e)}
340
-
341
- def bulk_update_components(self, params: Dict[str, Any]) -> Dict[str, Any]:
342
- """Update multiple components matching criteria."""
343
- try:
344
- if not self.current_schematic:
345
- return {"success": False, "error": "No schematic loaded"}
346
-
347
- criteria = params.get("criteria", {})
348
- updates = params.get("updates", {})
349
-
350
- if not criteria or not updates:
351
- return {"success": False, "error": "criteria and updates parameters required"}
352
-
353
- count = self.current_schematic.components.bulk_update(criteria, updates)
354
-
355
- return {"success": True, "message": f"Updated {count} components", "count": count}
356
- except Exception as e:
357
- logger.error(f"Error in bulk update: {e}")
358
- return {"success": False, "error": str(e)}
359
-
360
- def validate_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
361
- """Validate the current schematic."""
362
- try:
363
- if not self.current_schematic:
364
- return {"success": False, "error": "No schematic loaded"}
365
-
366
- issues = self.current_schematic.validate()
367
-
368
- # Categorize issues
369
- errors = [issue for issue in issues if issue.level.value in ("error", "critical")]
370
- warnings = [issue for issue in issues if issue.level.value == "warning"]
371
-
372
- return {
373
- "success": True,
374
- "valid": len(errors) == 0,
375
- "issue_count": len(issues),
376
- "errors": [str(issue) for issue in errors],
377
- "warnings": [str(issue) for issue in warnings],
378
- }
379
- except Exception as e:
380
- logger.error(f"Error validating schematic: {e}")
381
- return {"success": False, "error": str(e)}
382
-
383
- def get_schematic_summary(self, params: Dict[str, Any]) -> Dict[str, Any]:
384
- """Get summary of current schematic."""
385
- try:
386
- if not self.current_schematic:
387
- return {"success": False, "error": "No schematic loaded"}
388
-
389
- summary = self.current_schematic.get_summary()
390
-
391
- return {"success": True, "summary": summary}
392
- except Exception as e:
393
- logger.error(f"Error getting summary: {e}")
394
- return {"success": False, "error": str(e)}
395
-
396
- def search_library_symbols(self, params: Dict[str, Any]) -> Dict[str, Any]:
397
- """Search for symbols in libraries."""
398
- try:
399
- query = params.get("query")
400
- if not query:
401
- return {"success": False, "error": "query parameter required"}
402
-
403
- library = params.get("library")
404
- limit = params.get("limit", 20)
405
-
406
- symbols = self.symbol_cache.search_symbols(query, library, limit)
407
-
408
- symbol_results = []
409
- for symbol in symbols:
410
- symbol_results.append(
411
- {
412
- "lib_id": symbol.lib_id,
413
- "name": symbol.name,
414
- "library": symbol.library,
415
- "description": symbol.description,
416
- "reference_prefix": symbol.reference_prefix,
417
- "pin_count": len(symbol.pins),
418
- }
419
- )
420
-
421
- return {"success": True, "count": len(symbol_results), "symbols": symbol_results}
422
- except Exception as e:
423
- logger.error(f"Error searching symbols: {e}")
424
- return {"success": False, "error": str(e)}
425
-
426
- def add_library_path(self, params: Dict[str, Any]) -> Dict[str, Any]:
427
- """Add a custom library path."""
428
- try:
429
- library_path = params.get("library_path")
430
- if not library_path:
431
- return {"success": False, "error": "library_path parameter required"}
432
-
433
- success = self.symbol_cache.add_library_path(library_path)
434
-
435
- if success:
436
- return {"success": True, "message": f"Added library: {library_path}"}
437
- else:
438
- return {"success": False, "error": f"Failed to add library: {library_path}"}
439
- except Exception as e:
440
- logger.error(f"Error adding library: {e}")
441
- return {"success": False, "error": str(e)}
442
-
443
- def process_commands(self):
444
- """Main command processing loop."""
445
- logger.info("Starting command processing loop")
446
-
447
- try:
448
- for line in sys.stdin:
449
- try:
450
- # Parse command
451
- request = json.loads(line.strip())
452
- command = request.get("command")
453
- params = request.get("params", {})
454
- request_id = request.get("id")
455
-
456
- # Execute command
457
- if command in self.handlers:
458
- result = self.handlers[command](params)
459
- else:
460
- result = {"success": False, "error": f"Unknown command: {command}"}
461
-
462
- # Send response
463
- response = {"id": request_id, "result": result}
464
-
465
- print(json.dumps(response))
466
- sys.stdout.flush()
467
-
468
- except json.JSONDecodeError as e:
469
- logger.error(f"Invalid JSON input: {e}")
470
- error_response = {"id": None, "error": f"Invalid JSON: {e}"}
471
- print(json.dumps(error_response))
472
- sys.stdout.flush()
473
-
474
- except Exception as e:
475
- logger.error(f"Error processing command: {e}")
476
- logger.debug(traceback.format_exc())
477
- error_response = {
478
- "id": request.get("id") if "request" in locals() else None,
479
- "error": str(e),
480
- }
481
- print(json.dumps(error_response))
482
- sys.stdout.flush()
483
-
484
- except KeyboardInterrupt:
485
- logger.info("Received interrupt signal")
486
- except Exception as e:
487
- logger.error(f"Fatal error in command processing: {e}")
488
- logger.debug(traceback.format_exc())
489
- finally:
490
- logger.info("Command processing stopped")
491
-
492
-
493
- def main():
494
- """Main entry point."""
495
- interface = MCPInterface()
496
- interface.process_commands()
497
-
498
-
499
- if __name__ == "__main__":
500
- main()
@@ -1,20 +0,0 @@
1
- kicad_sch_api/__init__.py,sha256=kvc92hzeQ6mSe3AsXSbK-CvGgoXtljyUCrzJcmBa-ao,2821
2
- kicad_sch_api/py.typed,sha256=e4ldqxwpY7pNDG1olbvj4HSKr8sZ9vxgA_2ek8xXn-Q,70
3
- kicad_sch_api/core/__init__.py,sha256=ur_KeYBlGKl-e1hLpLdxAhGV2A-PCCGkcqd0r6KSeBA,566
4
- kicad_sch_api/core/components.py,sha256=_sw8Hh2eemGHzYcjGoFrBeufX05G1JfO5inYMQDQD9k,22100
5
- kicad_sch_api/core/formatter.py,sha256=MVF3Hhc5ZVPyVDYnGcxb88q0x0UTr2DQa45gppiFqNk,11953
6
- kicad_sch_api/core/parser.py,sha256=x7aMqnsDO4Y2VJtYvgrrJJVi2r8SAFUjSCVtMIDVWDY,16652
7
- kicad_sch_api/core/schematic.py,sha256=O76nZvj4qffHkFrMJV5Z35xU95efPW-_mtAD8Nni7ao,15553
8
- kicad_sch_api/core/types.py,sha256=VyzloTl4RbjMKj0TKu5rEZ-rtxtiT8nvQw8L6xawEvs,9980
9
- kicad_sch_api/library/__init__.py,sha256=NG9UTdcpn25Bl9tPsYs9ED7bvpaVPVdtLMbnxkQkOnU,250
10
- kicad_sch_api/library/cache.py,sha256=_JtzEGgO7ViIKF4W2zVrvmHQBIiosp9hOr9pG06Tw6I,18917
11
- kicad_sch_api/mcp/__init__.py,sha256=dVit9lqiieujSYkyvfycE8JTo0AEWMI4plNxCK9pSD0,128
12
- kicad_sch_api/mcp/server.py,sha256=wGTdXPF6mMA8JoP-HiDUg7XJ21Dfr0ZoThB3WqKM_fQ,19079
13
- kicad_sch_api/utils/__init__.py,sha256=1V_yGgI7jro6MUc4Pviux_WIeJ1wmiYFID186SZwWLQ,277
14
- kicad_sch_api/utils/validation.py,sha256=i7VvhMaYxrb8wxddf0S3vvpDE7r8ldM53CDqF9-ARqI,15557
15
- kicad_sch_api-0.0.1.dist-info/licenses/LICENSE,sha256=Em65Nvte1G9MHc0rHqtYuGkCPcshD588itTa358J6gs,1070
16
- kicad_sch_api-0.0.1.dist-info/METADATA,sha256=jrEKEblgqF95O2aXEdzpuZhGm1gFp4RFFe3DKU86GeQ,7643
17
- kicad_sch_api-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- kicad_sch_api-0.0.1.dist-info/entry_points.txt,sha256=VWKsFi2Jv7G_tmio3cNVhhIBfv_OZFaKa-T_ED84lc8,57
19
- kicad_sch_api-0.0.1.dist-info/top_level.txt,sha256=n0ex4gOJ1b_fARowcGqRzyOGZcHRhc5LZa6_vVgGxcI,14
20
- kicad_sch_api-0.0.1.dist-info/RECORD,,