kicad-sch-api 0.0.1__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 +112 -0
- kicad_sch_api/core/__init__.py +23 -0
- kicad_sch_api/core/components.py +652 -0
- kicad_sch_api/core/formatter.py +312 -0
- kicad_sch_api/core/parser.py +434 -0
- kicad_sch_api/core/schematic.py +478 -0
- kicad_sch_api/core/types.py +369 -0
- kicad_sch_api/library/__init__.py +10 -0
- kicad_sch_api/library/cache.py +548 -0
- kicad_sch_api/mcp/__init__.py +5 -0
- kicad_sch_api/mcp/server.py +500 -0
- kicad_sch_api/py.typed +1 -0
- kicad_sch_api/utils/__init__.py +15 -0
- kicad_sch_api/utils/validation.py +447 -0
- kicad_sch_api-0.0.1.dist-info/METADATA +226 -0
- kicad_sch_api-0.0.1.dist-info/RECORD +20 -0
- kicad_sch_api-0.0.1.dist-info/WHEEL +5 -0
- kicad_sch_api-0.0.1.dist-info/entry_points.txt +2 -0
- kicad_sch_api-0.0.1.dist-info/licenses/LICENSE +21 -0
- kicad_sch_api-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation utilities for KiCAD schematic manipulation.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive validation capabilities including error collection,
|
|
5
|
+
syntax validation, and semantic checking for schematic operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ValidationLevel(Enum):
|
|
18
|
+
"""Validation issue severity levels."""
|
|
19
|
+
|
|
20
|
+
INFO = "info"
|
|
21
|
+
WARNING = "warning"
|
|
22
|
+
ERROR = "error"
|
|
23
|
+
CRITICAL = "critical"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ValidationIssue:
|
|
28
|
+
"""Single validation issue with context."""
|
|
29
|
+
|
|
30
|
+
category: str # e.g., "syntax", "reference", "connection"
|
|
31
|
+
message: str # Human-readable description
|
|
32
|
+
level: ValidationLevel
|
|
33
|
+
context: Optional[Dict[str, Any]] = None # Additional context data
|
|
34
|
+
suggestion: Optional[str] = None # Suggested fix
|
|
35
|
+
|
|
36
|
+
def __post_init__(self):
|
|
37
|
+
if isinstance(self.level, str):
|
|
38
|
+
self.level = ValidationLevel(self.level)
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
context_str = f" ({self.context})" if self.context else ""
|
|
42
|
+
suggestion_str = f" Suggestion: {self.suggestion}" if self.suggestion else ""
|
|
43
|
+
return f"{self.level.value.upper()}: {self.category}: {self.message}{context_str}{suggestion_str}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ValidationError(Exception):
|
|
47
|
+
"""Exception raised when critical validation errors are found."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, message: str, issues: List[ValidationIssue] = None):
|
|
50
|
+
super().__init__(message)
|
|
51
|
+
self.issues = issues or []
|
|
52
|
+
|
|
53
|
+
def add_issue(self, issue: ValidationIssue):
|
|
54
|
+
"""Add a validation issue to this error."""
|
|
55
|
+
self.issues.append(issue)
|
|
56
|
+
|
|
57
|
+
def get_errors(self) -> List[ValidationIssue]:
|
|
58
|
+
"""Get only error-level issues."""
|
|
59
|
+
return [
|
|
60
|
+
issue
|
|
61
|
+
for issue in self.issues
|
|
62
|
+
if issue.level in (ValidationLevel.ERROR, ValidationLevel.CRITICAL)
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def get_warnings(self) -> List[ValidationIssue]:
|
|
66
|
+
"""Get only warning-level issues."""
|
|
67
|
+
return [issue for issue in self.issues if issue.level == ValidationLevel.WARNING]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SchematicValidator:
|
|
71
|
+
"""
|
|
72
|
+
Comprehensive validator for schematic data structures and operations.
|
|
73
|
+
|
|
74
|
+
Provides validation for:
|
|
75
|
+
- S-expression syntax
|
|
76
|
+
- Component references and properties
|
|
77
|
+
- Library references
|
|
78
|
+
- Basic electrical connectivity
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, strict: bool = False):
|
|
82
|
+
"""
|
|
83
|
+
Initialize validator.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
strict: If True, warnings are treated as errors
|
|
87
|
+
"""
|
|
88
|
+
self.strict = strict
|
|
89
|
+
self.issues = []
|
|
90
|
+
self._valid_reference_pattern = re.compile(r"^[A-Z]+[0-9]*$")
|
|
91
|
+
self._valid_lib_id_pattern = re.compile(r"^[^:]+:[^:]+$")
|
|
92
|
+
|
|
93
|
+
def validate_schematic_data(self, schematic_data: Dict[str, Any]) -> List[ValidationIssue]:
|
|
94
|
+
"""
|
|
95
|
+
Validate complete schematic data structure.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
schematic_data: Parsed schematic data
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of validation issues found
|
|
102
|
+
"""
|
|
103
|
+
self.issues.clear()
|
|
104
|
+
|
|
105
|
+
# Validate basic structure
|
|
106
|
+
self._validate_basic_structure(schematic_data)
|
|
107
|
+
|
|
108
|
+
# Validate components
|
|
109
|
+
components = schematic_data.get("components", [])
|
|
110
|
+
self._validate_components(components)
|
|
111
|
+
|
|
112
|
+
# Validate references are unique
|
|
113
|
+
self._validate_unique_references(components)
|
|
114
|
+
|
|
115
|
+
# Validate wires and connections
|
|
116
|
+
wires = schematic_data.get("wires", [])
|
|
117
|
+
self._validate_wires(wires)
|
|
118
|
+
|
|
119
|
+
# Validate nets
|
|
120
|
+
nets = schematic_data.get("nets", [])
|
|
121
|
+
self._validate_nets(nets, components)
|
|
122
|
+
|
|
123
|
+
return self.issues.copy()
|
|
124
|
+
|
|
125
|
+
def validate_component(self, component_data: Dict[str, Any]) -> List[ValidationIssue]:
|
|
126
|
+
"""Validate a single component."""
|
|
127
|
+
self.issues.clear()
|
|
128
|
+
self._validate_single_component(component_data)
|
|
129
|
+
return self.issues.copy()
|
|
130
|
+
|
|
131
|
+
def validate_reference(self, reference: str) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Validate component reference format.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
reference: Reference to validate (e.g., "R1", "U5")
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if reference is valid
|
|
140
|
+
"""
|
|
141
|
+
if not reference:
|
|
142
|
+
return False
|
|
143
|
+
return bool(self._valid_reference_pattern.match(reference))
|
|
144
|
+
|
|
145
|
+
def validate_lib_id(self, lib_id: str) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Validate library ID format.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
lib_id: Library ID to validate (e.g., "Device:R")
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True if lib_id is valid
|
|
154
|
+
"""
|
|
155
|
+
if not lib_id:
|
|
156
|
+
return False
|
|
157
|
+
return bool(self._valid_lib_id_pattern.match(lib_id))
|
|
158
|
+
|
|
159
|
+
def _validate_basic_structure(self, data: Dict[str, Any]):
|
|
160
|
+
"""Validate basic schematic structure."""
|
|
161
|
+
required_fields = ["version", "components"]
|
|
162
|
+
|
|
163
|
+
for field in required_fields:
|
|
164
|
+
if field not in data:
|
|
165
|
+
self.issues.append(
|
|
166
|
+
ValidationIssue(
|
|
167
|
+
category="structure",
|
|
168
|
+
message=f"Missing required field: {field}",
|
|
169
|
+
level=ValidationLevel.ERROR,
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Check version format
|
|
174
|
+
version = data.get("version")
|
|
175
|
+
if version and not isinstance(version, (int, str)):
|
|
176
|
+
self.issues.append(
|
|
177
|
+
ValidationIssue(
|
|
178
|
+
category="structure",
|
|
179
|
+
message=f"Invalid version type: {type(version)}",
|
|
180
|
+
level=ValidationLevel.WARNING,
|
|
181
|
+
suggestion="Version should be string or number",
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _validate_components(self, components: List[Dict[str, Any]]):
|
|
186
|
+
"""Validate all components in schematic."""
|
|
187
|
+
if not isinstance(components, list):
|
|
188
|
+
self.issues.append(
|
|
189
|
+
ValidationIssue(
|
|
190
|
+
category="components",
|
|
191
|
+
message="Components must be a list",
|
|
192
|
+
level=ValidationLevel.ERROR,
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
for i, component in enumerate(components):
|
|
198
|
+
try:
|
|
199
|
+
self._validate_single_component(component, f"component[{i}]")
|
|
200
|
+
except Exception as e:
|
|
201
|
+
self.issues.append(
|
|
202
|
+
ValidationIssue(
|
|
203
|
+
category="components",
|
|
204
|
+
message=f"Error validating component {i}: {e}",
|
|
205
|
+
level=ValidationLevel.ERROR,
|
|
206
|
+
context={"component_index": i},
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _validate_single_component(self, component: Dict[str, Any], context: str = "component"):
|
|
211
|
+
"""Validate a single component structure."""
|
|
212
|
+
required_fields = ["lib_id", "reference", "position"]
|
|
213
|
+
|
|
214
|
+
for field in required_fields:
|
|
215
|
+
if field not in component:
|
|
216
|
+
self.issues.append(
|
|
217
|
+
ValidationIssue(
|
|
218
|
+
category="component",
|
|
219
|
+
message=f"{context}: Missing required field: {field}",
|
|
220
|
+
level=ValidationLevel.ERROR,
|
|
221
|
+
context={"field": field},
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Validate reference format
|
|
226
|
+
reference = component.get("reference")
|
|
227
|
+
if reference and not self.validate_reference(reference):
|
|
228
|
+
self.issues.append(
|
|
229
|
+
ValidationIssue(
|
|
230
|
+
category="reference",
|
|
231
|
+
message=f"{context}: Invalid reference format: {reference}",
|
|
232
|
+
level=ValidationLevel.ERROR,
|
|
233
|
+
context={"reference": reference},
|
|
234
|
+
suggestion="Reference should match pattern: [A-Z]+[0-9]*",
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Validate lib_id format
|
|
239
|
+
lib_id = component.get("lib_id")
|
|
240
|
+
if lib_id and not self.validate_lib_id(lib_id):
|
|
241
|
+
self.issues.append(
|
|
242
|
+
ValidationIssue(
|
|
243
|
+
category="lib_id",
|
|
244
|
+
message=f"{context}: Invalid lib_id format: {lib_id}",
|
|
245
|
+
level=ValidationLevel.ERROR,
|
|
246
|
+
context={"lib_id": lib_id},
|
|
247
|
+
suggestion="lib_id should match pattern: Library:Symbol",
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Validate position
|
|
252
|
+
position = component.get("position")
|
|
253
|
+
if position:
|
|
254
|
+
self._validate_position(position, f"{context}.position")
|
|
255
|
+
|
|
256
|
+
# Validate UUID if present
|
|
257
|
+
uuid = component.get("uuid")
|
|
258
|
+
if uuid and not self._validate_uuid(uuid):
|
|
259
|
+
self.issues.append(
|
|
260
|
+
ValidationIssue(
|
|
261
|
+
category="uuid",
|
|
262
|
+
message=f"{context}: Invalid UUID format: {uuid}",
|
|
263
|
+
level=ValidationLevel.WARNING,
|
|
264
|
+
context={"uuid": uuid},
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def _validate_position(self, position: Any, context: str):
|
|
269
|
+
"""Validate position data."""
|
|
270
|
+
if hasattr(position, "x") and hasattr(position, "y"):
|
|
271
|
+
# Point object
|
|
272
|
+
try:
|
|
273
|
+
float(position.x)
|
|
274
|
+
float(position.y)
|
|
275
|
+
except (TypeError, ValueError):
|
|
276
|
+
self.issues.append(
|
|
277
|
+
ValidationIssue(
|
|
278
|
+
category="position",
|
|
279
|
+
message=f"{context}: Position coordinates must be numeric",
|
|
280
|
+
level=ValidationLevel.ERROR,
|
|
281
|
+
context={"position": str(position)},
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
else:
|
|
285
|
+
self.issues.append(
|
|
286
|
+
ValidationIssue(
|
|
287
|
+
category="position",
|
|
288
|
+
message=f"{context}: Invalid position format",
|
|
289
|
+
level=ValidationLevel.ERROR,
|
|
290
|
+
context={"position": position},
|
|
291
|
+
suggestion="Position should be Point object with x,y coordinates",
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def _validate_unique_references(self, components: List[Dict[str, Any]]):
|
|
296
|
+
"""Validate that all component references are unique."""
|
|
297
|
+
references = []
|
|
298
|
+
duplicates = set()
|
|
299
|
+
|
|
300
|
+
for component in components:
|
|
301
|
+
ref = component.get("reference")
|
|
302
|
+
if ref:
|
|
303
|
+
if ref in references:
|
|
304
|
+
duplicates.add(ref)
|
|
305
|
+
references.append(ref)
|
|
306
|
+
|
|
307
|
+
for ref in duplicates:
|
|
308
|
+
self.issues.append(
|
|
309
|
+
ValidationIssue(
|
|
310
|
+
category="reference",
|
|
311
|
+
message=f"Duplicate component reference: {ref}",
|
|
312
|
+
level=ValidationLevel.ERROR,
|
|
313
|
+
context={"reference": ref},
|
|
314
|
+
suggestion="Each component must have a unique reference",
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _validate_wires(self, wires: List[Dict[str, Any]]):
|
|
319
|
+
"""Validate wire connections."""
|
|
320
|
+
for i, wire in enumerate(wires):
|
|
321
|
+
context = f"wire[{i}]"
|
|
322
|
+
|
|
323
|
+
# Validate start and end points
|
|
324
|
+
for point_name in ["start", "end"]:
|
|
325
|
+
point = wire.get(point_name)
|
|
326
|
+
if point:
|
|
327
|
+
self._validate_position(point, f"{context}.{point_name}")
|
|
328
|
+
|
|
329
|
+
# Validate wire has valid length
|
|
330
|
+
start = wire.get("start")
|
|
331
|
+
end = wire.get("end")
|
|
332
|
+
if start and end and hasattr(start, "x") and hasattr(end, "x"):
|
|
333
|
+
if start.x == end.x and start.y == end.y:
|
|
334
|
+
self.issues.append(
|
|
335
|
+
ValidationIssue(
|
|
336
|
+
category="wire",
|
|
337
|
+
message=f"{context}: Wire has zero length",
|
|
338
|
+
level=ValidationLevel.WARNING,
|
|
339
|
+
context={"start": str(start), "end": str(end)},
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def _validate_nets(self, nets: List[Dict[str, Any]], components: List[Dict[str, Any]]):
|
|
344
|
+
"""Validate electrical nets."""
|
|
345
|
+
component_refs = {comp.get("reference") for comp in components if comp.get("reference")}
|
|
346
|
+
|
|
347
|
+
for i, net in enumerate(nets):
|
|
348
|
+
context = f"net[{i}]"
|
|
349
|
+
|
|
350
|
+
# Validate net has connections
|
|
351
|
+
connections = net.get("components", [])
|
|
352
|
+
if not connections:
|
|
353
|
+
self.issues.append(
|
|
354
|
+
ValidationIssue(
|
|
355
|
+
category="net",
|
|
356
|
+
message=f"{context}: Net has no connections",
|
|
357
|
+
level=ValidationLevel.WARNING,
|
|
358
|
+
context={"net_name": net.get("name", "unnamed")},
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Validate all referenced components exist
|
|
363
|
+
for ref, pin in connections:
|
|
364
|
+
if ref not in component_refs:
|
|
365
|
+
self.issues.append(
|
|
366
|
+
ValidationIssue(
|
|
367
|
+
category="net",
|
|
368
|
+
message=f"{context}: References non-existent component: {ref}",
|
|
369
|
+
level=ValidationLevel.ERROR,
|
|
370
|
+
context={"reference": ref, "net_name": net.get("name", "unnamed")},
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def _validate_uuid(self, uuid_str: str) -> bool:
|
|
375
|
+
"""Validate UUID format."""
|
|
376
|
+
uuid_pattern = re.compile(
|
|
377
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE
|
|
378
|
+
)
|
|
379
|
+
return bool(uuid_pattern.match(uuid_str))
|
|
380
|
+
|
|
381
|
+
def has_errors(self) -> bool:
|
|
382
|
+
"""Check if any error-level issues were found."""
|
|
383
|
+
return any(
|
|
384
|
+
issue.level in (ValidationLevel.ERROR, ValidationLevel.CRITICAL)
|
|
385
|
+
for issue in self.issues
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def has_warnings(self) -> bool:
|
|
389
|
+
"""Check if any warning-level issues were found."""
|
|
390
|
+
return any(issue.level == ValidationLevel.WARNING for issue in self.issues)
|
|
391
|
+
|
|
392
|
+
def get_summary(self) -> Dict[str, int]:
|
|
393
|
+
"""Get summary of validation issues by level."""
|
|
394
|
+
summary = {level.value: 0 for level in ValidationLevel}
|
|
395
|
+
for issue in self.issues:
|
|
396
|
+
summary[issue.level.value] += 1
|
|
397
|
+
return summary
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def validate_schematic_file(file_path: str) -> List[ValidationIssue]:
|
|
401
|
+
"""
|
|
402
|
+
Convenience function to validate a schematic file.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
file_path: Path to .kicad_sch file
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
List of validation issues
|
|
409
|
+
"""
|
|
410
|
+
from ..core.parser import SExpressionParser
|
|
411
|
+
|
|
412
|
+
parser = SExpressionParser()
|
|
413
|
+
try:
|
|
414
|
+
schematic_data = parser.parse_file(file_path)
|
|
415
|
+
validator = SchematicValidator()
|
|
416
|
+
return validator.validate_schematic_data(schematic_data)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
return [
|
|
419
|
+
ValidationIssue(
|
|
420
|
+
category="file",
|
|
421
|
+
message=f"Failed to validate file {file_path}: {e}",
|
|
422
|
+
level=ValidationLevel.CRITICAL,
|
|
423
|
+
)
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def collect_validation_errors(func):
|
|
428
|
+
"""
|
|
429
|
+
Decorator to collect validation errors from operations.
|
|
430
|
+
|
|
431
|
+
Usage:
|
|
432
|
+
@collect_validation_errors
|
|
433
|
+
def my_operation():
|
|
434
|
+
# ... operation that might have validation issues
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
def wrapper(*args, **kwargs):
|
|
438
|
+
try:
|
|
439
|
+
return func(*args, **kwargs)
|
|
440
|
+
except ValidationError as e:
|
|
441
|
+
logger.error(f"Validation failed in {func.__name__}: {e}")
|
|
442
|
+
# Log individual issues
|
|
443
|
+
for issue in e.issues:
|
|
444
|
+
logger.warning(f" {issue}")
|
|
445
|
+
raise
|
|
446
|
+
|
|
447
|
+
return wrapper
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kicad-sch-api
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Professional KiCAD schematic manipulation library with exact format preservation and AI agent integration
|
|
5
|
+
Author-email: Circuit-Synth <info@circuit-synth.com>
|
|
6
|
+
Maintainer-email: Circuit-Synth <info@circuit-synth.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/circuit-synth/kicad-sch-api
|
|
9
|
+
Project-URL: Documentation, https://circuit-synth.github.io/kicad-sch-api/
|
|
10
|
+
Project-URL: Repository, https://github.com/circuit-synth/kicad-sch-api.git
|
|
11
|
+
Project-URL: Bug Reports, https://github.com/circuit-synth/kicad-sch-api/issues
|
|
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
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Operating System :: OS Independent
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: sexpdata>=0.0.3
|
|
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
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
42
|
+
Provides-Extra: docs
|
|
43
|
+
Requires-Dist: sphinx>=5.0.0; extra == "docs"
|
|
44
|
+
Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
|
|
45
|
+
Requires-Dist: myst-parser>=0.18.0; extra == "docs"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# kicad-sch-api
|
|
49
|
+
|
|
50
|
+
**Professional KiCAD Schematic Manipulation Library with AI Agent Integration**
|
|
51
|
+
|
|
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
|
+
|
|
54
|
+
## ๐ Key Features
|
|
55
|
+
|
|
56
|
+
- **๐ Exact Format Preservation**: Output matches KiCAD's native formatting exactly
|
|
57
|
+
- **โก High Performance**: Optimized for large schematics with symbol caching
|
|
58
|
+
- **๐ค AI Agent Integration**: Native MCP server for seamless AI agent interaction
|
|
59
|
+
- **๐ง Enhanced API**: Intuitive object-oriented interface with bulk operations
|
|
60
|
+
- **๐ Advanced Library Management**: Multi-source symbol lookup and caching
|
|
61
|
+
- **โ
Professional Validation**: Comprehensive error collection and reporting
|
|
62
|
+
- **๐ฏ KiCAD 9 Optimized**: Built specifically for latest KiCAD format
|
|
63
|
+
|
|
64
|
+
## ๐ vs. Existing Solutions
|
|
65
|
+
|
|
66
|
+
| Feature | kicad-sch-api | kicad-skip | KiCAD Official API |
|
|
67
|
+
|---------|---------------|------------|-------------------|
|
|
68
|
+
| **Schematic Support** | โ
Full | โ
Full | โ PCB Only |
|
|
69
|
+
| **Format Preservation** | โ
Exact | โ Basic | N/A |
|
|
70
|
+
| **Performance** | โ
Optimized | โ ๏ธ Basic | N/A |
|
|
71
|
+
| **AI Integration** | โ
Native MCP | โ None | โ None |
|
|
72
|
+
| **Library Management** | โ
Advanced | โ ๏ธ Basic | N/A |
|
|
73
|
+
| **Runtime Dependencies** | โ None | โ None | โ
KiCAD Required |
|
|
74
|
+
|
|
75
|
+
## ๐ฆ Installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Install from PyPI (coming soon)
|
|
79
|
+
pip install kicad-sch-api
|
|
80
|
+
|
|
81
|
+
# Or install from source
|
|
82
|
+
git clone https://github.com/circuit-synth/kicad-sch-api.git
|
|
83
|
+
cd kicad-sch-api/python
|
|
84
|
+
pip install -e .
|
|
85
|
+
|
|
86
|
+
# For AI agent integration (MCP server)
|
|
87
|
+
cd ../mcp-server
|
|
88
|
+
npm install
|
|
89
|
+
npm run build
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## ๐ฏ Quick Start
|
|
93
|
+
|
|
94
|
+
### Basic Schematic Manipulation
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import kicad_sch_api as ksa
|
|
98
|
+
|
|
99
|
+
# Load existing schematic
|
|
100
|
+
sch = ksa.load_schematic('my_circuit.kicad_sch')
|
|
101
|
+
|
|
102
|
+
# Add components
|
|
103
|
+
resistor = sch.components.add('Device:R', ref='R1', value='10k', pos=(100, 100))
|
|
104
|
+
capacitor = sch.components.add('Device:C', ref='C1', value='0.1uF', pos=(150, 100))
|
|
105
|
+
|
|
106
|
+
# Update properties
|
|
107
|
+
resistor.footprint = 'Resistor_SMD:R_0603_1608Metric'
|
|
108
|
+
resistor.set_property('MPN', 'RC0603FR-0710KL')
|
|
109
|
+
|
|
110
|
+
# Connect components
|
|
111
|
+
sch.add_wire(resistor.get_pin_position('2'), capacitor.get_pin_position('1'))
|
|
112
|
+
|
|
113
|
+
# Save with exact format preservation
|
|
114
|
+
sch.save() # Preserves original formatting exactly
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Advanced Operations
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Bulk operations for large schematics
|
|
121
|
+
resistors = sch.components.filter(lib_id='Device:R')
|
|
122
|
+
for r in resistors:
|
|
123
|
+
r.set_property('Tolerance', '1%')
|
|
124
|
+
|
|
125
|
+
# Search and analysis
|
|
126
|
+
power_components = sch.components.in_area(0, 0, 50, 50)
|
|
127
|
+
high_value_resistors = sch.components.filter(
|
|
128
|
+
lib_id='Device:R',
|
|
129
|
+
value_pattern='*k' # Components with 'k' in value
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Validation and error checking
|
|
133
|
+
issues = sch.validate()
|
|
134
|
+
if issues:
|
|
135
|
+
print(f"Found {len(issues)} validation issues:")
|
|
136
|
+
for issue in issues:
|
|
137
|
+
print(f" {issue}")
|
|
138
|
+
|
|
139
|
+
# Performance statistics
|
|
140
|
+
stats = sch.get_performance_stats()
|
|
141
|
+
print(f"Cache hit rate: {stats['symbol_cache']['hit_rate_percent']}%")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### AI Agent Integration (MCP)
|
|
145
|
+
|
|
146
|
+
Configure in Claude Desktop or compatible MCP client:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"kicad-sch": {
|
|
151
|
+
"command": "node",
|
|
152
|
+
"args": ["/path/to/kicad-sch-api/mcp-server/dist/index.js"],
|
|
153
|
+
"env": {
|
|
154
|
+
"PYTHON_PATH": "python3",
|
|
155
|
+
"KICAD_SCH_API_PATH": "/path/to/kicad-sch-api/python"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Then use natural language with your AI agent:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
User: "Create a voltage divider circuit with two 10k resistors"
|
|
165
|
+
|
|
166
|
+
Claude: I'll create a voltage divider circuit for you.
|
|
167
|
+
|
|
168
|
+
[Agent automatically uses MCP tools to:]
|
|
169
|
+
1. Create new schematic
|
|
170
|
+
2. Add R1 (10k resistor) at (100, 100)
|
|
171
|
+
3. Add R2 (10k resistor) at (100, 150)
|
|
172
|
+
4. Connect components with wires
|
|
173
|
+
5. Add voltage input and output labels
|
|
174
|
+
6. Save schematic with exact formatting
|
|
175
|
+
|
|
176
|
+
Your voltage divider circuit is ready! The circuit provides 50% voltage division
|
|
177
|
+
with two 10kฮฉ resistors in series configuration.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## ๐๏ธ Architecture
|
|
181
|
+
|
|
182
|
+
The library consists of two main components:
|
|
183
|
+
|
|
184
|
+
### Python Library (Core)
|
|
185
|
+
- **Enhanced Object Model**: Intuitive API with fast component collections
|
|
186
|
+
- **Exact Format Preservation**: S-expression writer that matches KiCAD output
|
|
187
|
+
- **Symbol Caching**: High-performance library symbol management
|
|
188
|
+
- **Comprehensive Validation**: Error collection and professional reporting
|
|
189
|
+
|
|
190
|
+
### MCP Server (AI Integration)
|
|
191
|
+
- **TypeScript MCP Server**: Implements Anthropic's MCP specification
|
|
192
|
+
- **Python Bridge**: Reliable subprocess communication
|
|
193
|
+
- **Comprehensive Tools**: 15+ tools for complete schematic manipulation
|
|
194
|
+
- **Professional Error Handling**: Detailed error context for AI agents
|
|
195
|
+
|
|
196
|
+
## ๐งช Testing & Quality
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Python tests
|
|
200
|
+
cd python
|
|
201
|
+
python -m pytest tests/ -v --cov=kicad_sch_api
|
|
202
|
+
|
|
203
|
+
# MCP server tests
|
|
204
|
+
cd mcp-server
|
|
205
|
+
npm test
|
|
206
|
+
|
|
207
|
+
# Format preservation tests
|
|
208
|
+
python -m pytest tests/test_format_preservation.py -v
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## ๐ค Contributing
|
|
212
|
+
|
|
213
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
214
|
+
|
|
215
|
+
## ๐ License
|
|
216
|
+
|
|
217
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
218
|
+
|
|
219
|
+
## ๐ Related Projects
|
|
220
|
+
|
|
221
|
+
- **[circuit-synth](https://github.com/circuit-synth/circuit-synth)**: Comprehensive circuit design automation
|
|
222
|
+
- **[kicad-skip](https://github.com/psychogenic/kicad-skip)**: Foundation S-expression parser
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
**Built with โค๏ธ by the Circuit-Synth team**
|
|
@@ -0,0 +1,20 @@
|
|
|
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,,
|