kicad-sch-api 0.3.0__py3-none-any.whl → 0.5.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.
Files changed (112) hide show
  1. kicad_sch_api/__init__.py +68 -3
  2. kicad_sch_api/cli/__init__.py +45 -0
  3. kicad_sch_api/cli/base.py +302 -0
  4. kicad_sch_api/cli/bom.py +164 -0
  5. kicad_sch_api/cli/erc.py +229 -0
  6. kicad_sch_api/cli/export_docs.py +289 -0
  7. kicad_sch_api/cli/kicad_to_python.py +169 -0
  8. kicad_sch_api/cli/netlist.py +94 -0
  9. kicad_sch_api/cli/types.py +43 -0
  10. kicad_sch_api/collections/__init__.py +36 -0
  11. kicad_sch_api/collections/base.py +604 -0
  12. kicad_sch_api/collections/components.py +1623 -0
  13. kicad_sch_api/collections/junctions.py +206 -0
  14. kicad_sch_api/collections/labels.py +508 -0
  15. kicad_sch_api/collections/wires.py +292 -0
  16. kicad_sch_api/core/__init__.py +37 -2
  17. kicad_sch_api/core/collections/__init__.py +5 -0
  18. kicad_sch_api/core/collections/base.py +248 -0
  19. kicad_sch_api/core/component_bounds.py +34 -7
  20. kicad_sch_api/core/components.py +213 -52
  21. kicad_sch_api/core/config.py +110 -15
  22. kicad_sch_api/core/connectivity.py +692 -0
  23. kicad_sch_api/core/exceptions.py +175 -0
  24. kicad_sch_api/core/factories/__init__.py +5 -0
  25. kicad_sch_api/core/factories/element_factory.py +278 -0
  26. kicad_sch_api/core/formatter.py +60 -9
  27. kicad_sch_api/core/geometry.py +94 -5
  28. kicad_sch_api/core/junctions.py +26 -75
  29. kicad_sch_api/core/labels.py +324 -0
  30. kicad_sch_api/core/managers/__init__.py +30 -0
  31. kicad_sch_api/core/managers/base.py +76 -0
  32. kicad_sch_api/core/managers/file_io.py +246 -0
  33. kicad_sch_api/core/managers/format_sync.py +502 -0
  34. kicad_sch_api/core/managers/graphics.py +580 -0
  35. kicad_sch_api/core/managers/hierarchy.py +661 -0
  36. kicad_sch_api/core/managers/metadata.py +271 -0
  37. kicad_sch_api/core/managers/sheet.py +492 -0
  38. kicad_sch_api/core/managers/text_elements.py +537 -0
  39. kicad_sch_api/core/managers/validation.py +476 -0
  40. kicad_sch_api/core/managers/wire.py +410 -0
  41. kicad_sch_api/core/nets.py +305 -0
  42. kicad_sch_api/core/no_connects.py +252 -0
  43. kicad_sch_api/core/parser.py +194 -970
  44. kicad_sch_api/core/parsing_utils.py +63 -0
  45. kicad_sch_api/core/pin_utils.py +103 -9
  46. kicad_sch_api/core/schematic.py +1328 -1079
  47. kicad_sch_api/core/texts.py +316 -0
  48. kicad_sch_api/core/types.py +159 -23
  49. kicad_sch_api/core/wires.py +27 -75
  50. kicad_sch_api/exporters/__init__.py +10 -0
  51. kicad_sch_api/exporters/python_generator.py +610 -0
  52. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  53. kicad_sch_api/geometry/__init__.py +38 -0
  54. kicad_sch_api/geometry/font_metrics.py +22 -0
  55. kicad_sch_api/geometry/routing.py +211 -0
  56. kicad_sch_api/geometry/symbol_bbox.py +608 -0
  57. kicad_sch_api/interfaces/__init__.py +17 -0
  58. kicad_sch_api/interfaces/parser.py +76 -0
  59. kicad_sch_api/interfaces/repository.py +70 -0
  60. kicad_sch_api/interfaces/resolver.py +117 -0
  61. kicad_sch_api/parsers/__init__.py +14 -0
  62. kicad_sch_api/parsers/base.py +145 -0
  63. kicad_sch_api/parsers/elements/__init__.py +22 -0
  64. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  65. kicad_sch_api/parsers/elements/label_parser.py +216 -0
  66. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  67. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  68. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  69. kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
  70. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  71. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  72. kicad_sch_api/parsers/registry.py +155 -0
  73. kicad_sch_api/parsers/utils.py +80 -0
  74. kicad_sch_api/symbols/__init__.py +18 -0
  75. kicad_sch_api/symbols/cache.py +467 -0
  76. kicad_sch_api/symbols/resolver.py +361 -0
  77. kicad_sch_api/symbols/validators.py +504 -0
  78. kicad_sch_api/utils/logging.py +555 -0
  79. kicad_sch_api/utils/logging_decorators.py +587 -0
  80. kicad_sch_api/utils/validation.py +16 -22
  81. kicad_sch_api/validation/__init__.py +25 -0
  82. kicad_sch_api/validation/erc.py +171 -0
  83. kicad_sch_api/validation/erc_models.py +203 -0
  84. kicad_sch_api/validation/pin_matrix.py +243 -0
  85. kicad_sch_api/validation/validators.py +391 -0
  86. kicad_sch_api/wrappers/__init__.py +14 -0
  87. kicad_sch_api/wrappers/base.py +89 -0
  88. kicad_sch_api/wrappers/wire.py +198 -0
  89. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  90. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  91. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  92. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  93. mcp_server/__init__.py +34 -0
  94. mcp_server/example_logging_integration.py +506 -0
  95. mcp_server/models.py +252 -0
  96. mcp_server/server.py +357 -0
  97. mcp_server/tools/__init__.py +32 -0
  98. mcp_server/tools/component_tools.py +516 -0
  99. mcp_server/tools/connectivity_tools.py +532 -0
  100. mcp_server/tools/consolidated_tools.py +1216 -0
  101. mcp_server/tools/pin_discovery.py +333 -0
  102. mcp_server/utils/__init__.py +38 -0
  103. mcp_server/utils/logging.py +127 -0
  104. mcp_server/utils.py +36 -0
  105. kicad_sch_api/core/manhattan_routing.py +0 -430
  106. kicad_sch_api/core/simple_manhattan.py +0 -228
  107. kicad_sch_api/core/wire_routing.py +0 -380
  108. kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
  109. kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
  110. kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
  111. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  112. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,504 @@
1
+ """
2
+ Symbol validation for KiCAD symbol definitions.
3
+
4
+ Provides comprehensive validation for symbol definitions, inheritance chains,
5
+ and symbol data integrity.
6
+ """
7
+
8
+ import logging
9
+ from typing import Any, Dict, List, Optional, Set, Tuple
10
+
11
+ from ..library.cache import SymbolDefinition
12
+ from ..utils.validation import ValidationError, ValidationIssue
13
+ from .cache import ISymbolCache
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class SymbolValidator:
19
+ """
20
+ Comprehensive validator for symbol definitions and inheritance.
21
+
22
+ Provides detailed validation with specific error reporting for
23
+ symbol issues that could affect schematic generation.
24
+ """
25
+
26
+ def __init__(self, cache: Optional[ISymbolCache] = None):
27
+ """
28
+ Initialize symbol validator.
29
+
30
+ Args:
31
+ cache: Optional symbol cache for inheritance validation
32
+ """
33
+ self._cache = cache
34
+ self._validation_rules = self._initialize_validation_rules()
35
+
36
+ def validate_symbol(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
37
+ """
38
+ Validate a symbol definition comprehensively.
39
+
40
+ Args:
41
+ symbol: Symbol to validate
42
+
43
+ Returns:
44
+ List of validation issues found
45
+ """
46
+ issues = []
47
+
48
+ # Run all validation rules
49
+ for rule_name, rule_func in self._validation_rules.items():
50
+ try:
51
+ rule_issues = rule_func(symbol)
52
+ issues.extend(rule_issues)
53
+ except Exception as e:
54
+ issues.append(
55
+ ValidationIssue(
56
+ category="validation",
57
+ message=f"Validation rule '{rule_name}' failed: {e}",
58
+ level="error",
59
+ context={"symbol": symbol.lib_id, "rule": rule_name},
60
+ )
61
+ )
62
+
63
+ return issues
64
+
65
+ def validate_lib_id(self, lib_id: str) -> bool:
66
+ """
67
+ Validate library ID format.
68
+
69
+ Args:
70
+ lib_id: Library identifier to validate
71
+
72
+ Returns:
73
+ True if lib_id format is valid
74
+ """
75
+ if not lib_id or not isinstance(lib_id, str):
76
+ return False
77
+
78
+ if ":" not in lib_id:
79
+ return False
80
+
81
+ parts = lib_id.split(":")
82
+ if len(parts) != 2:
83
+ return False
84
+
85
+ library_name, symbol_name = parts
86
+ return bool(library_name.strip() and symbol_name.strip())
87
+
88
+ def validate_inheritance_chain(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
89
+ """
90
+ Validate symbol inheritance chain for cycles and missing parents.
91
+
92
+ Args:
93
+ symbol: Symbol to validate inheritance for
94
+
95
+ Returns:
96
+ List of inheritance-related validation issues
97
+ """
98
+ issues = []
99
+
100
+ if not symbol.extends:
101
+ return issues
102
+
103
+ if not self._cache:
104
+ issues.append(
105
+ ValidationIssue(
106
+ category="inheritance",
107
+ message="Cannot validate inheritance without cache",
108
+ level="warning",
109
+ context={"symbol": symbol.lib_id},
110
+ )
111
+ )
112
+ return issues
113
+
114
+ # Check for inheritance chain issues
115
+ visited = set()
116
+ current_lib_id = symbol.lib_id
117
+
118
+ while current_lib_id:
119
+ if current_lib_id in visited:
120
+ issues.append(
121
+ ValidationIssue(
122
+ category="inheritance",
123
+ message=f"Circular inheritance detected in chain starting from {symbol.lib_id}",
124
+ level="error",
125
+ context={"symbol": symbol.lib_id, "cycle_point": current_lib_id},
126
+ )
127
+ )
128
+ break
129
+
130
+ visited.add(current_lib_id)
131
+ current_symbol = self._cache.get_symbol(current_lib_id)
132
+
133
+ if not current_symbol:
134
+ issues.append(
135
+ ValidationIssue(
136
+ category="inheritance",
137
+ message=f"Missing symbol in inheritance chain: {current_lib_id}",
138
+ level="error",
139
+ context={"symbol": symbol.lib_id, "missing": current_lib_id},
140
+ )
141
+ )
142
+ break
143
+
144
+ if not current_symbol.extends:
145
+ break
146
+
147
+ # Resolve parent lib_id
148
+ parent_name = current_symbol.extends
149
+ if ":" in parent_name:
150
+ current_lib_id = parent_name
151
+ else:
152
+ current_lib_id = f"{current_symbol.library}:{parent_name}"
153
+
154
+ # Check if parent exists
155
+ if not self._cache.has_symbol(current_lib_id):
156
+ issues.append(
157
+ ValidationIssue(
158
+ category="inheritance",
159
+ message=f"Parent symbol not found: {current_lib_id}",
160
+ level="error",
161
+ context={"symbol": symbol.lib_id, "parent": current_lib_id},
162
+ )
163
+ )
164
+ break
165
+
166
+ return issues
167
+
168
+ def validate_symbol_integrity(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
169
+ """
170
+ Validate symbol data integrity and consistency.
171
+
172
+ Args:
173
+ symbol: Symbol to validate
174
+
175
+ Returns:
176
+ List of integrity validation issues
177
+ """
178
+ issues = []
179
+
180
+ # Validate pin integrity
181
+ pin_issues = self._validate_pins(symbol)
182
+ issues.extend(pin_issues)
183
+
184
+ # Validate graphic elements
185
+ graphics_issues = self._validate_graphics(symbol)
186
+ issues.extend(graphics_issues)
187
+
188
+ # Validate units
189
+ units_issues = self._validate_units(symbol)
190
+ issues.extend(units_issues)
191
+
192
+ return issues
193
+
194
+ def _initialize_validation_rules(self) -> Dict[str, callable]:
195
+ """Initialize all validation rules."""
196
+ return {
197
+ "lib_id_format": self._validate_lib_id_format,
198
+ "required_fields": self._validate_required_fields,
199
+ "reference_prefix": self._validate_reference_prefix,
200
+ "pin_consistency": self._validate_pin_consistency,
201
+ "pin_details": self._validate_pins,
202
+ "unit_consistency": self._validate_unit_consistency,
203
+ "unit_details": self._validate_units,
204
+ "extends_format": self._validate_extends_format,
205
+ }
206
+
207
+ def _validate_lib_id_format(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
208
+ """Validate lib_id format."""
209
+ issues = []
210
+
211
+ if not self.validate_lib_id(symbol.lib_id):
212
+ issues.append(
213
+ ValidationIssue(
214
+ category="lib_id",
215
+ message=f"Invalid lib_id format: {symbol.lib_id}",
216
+ level="error",
217
+ context={"symbol": symbol.lib_id},
218
+ )
219
+ )
220
+
221
+ return issues
222
+
223
+ def _validate_required_fields(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
224
+ """Validate required symbol fields."""
225
+ issues = []
226
+
227
+ if not symbol.name:
228
+ issues.append(
229
+ ValidationIssue(
230
+ category="required_fields",
231
+ message="Symbol name is required",
232
+ level="error",
233
+ context={"symbol": symbol.lib_id},
234
+ )
235
+ )
236
+
237
+ if not symbol.library:
238
+ issues.append(
239
+ ValidationIssue(
240
+ category="required_fields",
241
+ message="Symbol library is required",
242
+ level="error",
243
+ context={"symbol": symbol.lib_id},
244
+ )
245
+ )
246
+
247
+ if not symbol.reference_prefix:
248
+ issues.append(
249
+ ValidationIssue(
250
+ category="required_fields",
251
+ message="Symbol reference prefix is missing",
252
+ level="warning",
253
+ context={"symbol": symbol.lib_id},
254
+ )
255
+ )
256
+
257
+ return issues
258
+
259
+ def _validate_reference_prefix(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
260
+ """Validate reference prefix format."""
261
+ issues = []
262
+
263
+ if symbol.reference_prefix:
264
+ # Check for invalid characters
265
+ invalid_chars = set(symbol.reference_prefix) - set(
266
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
267
+ )
268
+ if invalid_chars:
269
+ issues.append(
270
+ ValidationIssue(
271
+ category="reference_prefix",
272
+ message=f"Reference prefix contains invalid characters: {invalid_chars}",
273
+ level="warning",
274
+ context={"symbol": symbol.lib_id, "prefix": symbol.reference_prefix},
275
+ )
276
+ )
277
+
278
+ # Check for common patterns
279
+ if symbol.reference_prefix.lower() in ["u", "ic"] and not symbol.description:
280
+ issues.append(
281
+ ValidationIssue(
282
+ category="reference_prefix",
283
+ message="Generic IC prefix 'U' - consider adding description",
284
+ level="info",
285
+ context={"symbol": symbol.lib_id},
286
+ )
287
+ )
288
+
289
+ return issues
290
+
291
+ def _validate_pin_consistency(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
292
+ """Validate pin consistency and numbering."""
293
+ issues = []
294
+
295
+ if not symbol.pins:
296
+ issues.append(
297
+ ValidationIssue(
298
+ category="symbol",
299
+ level="warning",
300
+ message="Symbol has no pins defined",
301
+ context={"symbol": symbol.lib_id},
302
+ )
303
+ )
304
+ return issues
305
+
306
+ # Check for duplicate pin numbers
307
+ pin_numbers = [pin.number for pin in symbol.pins]
308
+ duplicates = set([num for num in pin_numbers if pin_numbers.count(num) > 1])
309
+
310
+ if duplicates:
311
+ issues.append(
312
+ ValidationIssue(
313
+ category="symbol",
314
+ level="error",
315
+ message=f"Duplicate pin numbers found: {duplicates}",
316
+ context={"symbol": symbol.lib_id, "duplicates": list(duplicates)},
317
+ )
318
+ )
319
+
320
+ # Check for pins with same position
321
+ pin_positions = [(pin.position.x, pin.position.y) for pin in symbol.pins]
322
+ for i, pos1 in enumerate(pin_positions):
323
+ for j, pos2 in enumerate(pin_positions[i + 1 :], i + 1):
324
+ if pos1 == pos2:
325
+ issues.append(
326
+ ValidationIssue(
327
+ category="symbol",
328
+ level="warning",
329
+ message=f"Pins at same position: {symbol.pins[i].number} and {symbol.pins[j].number}",
330
+ context={"symbol": symbol.lib_id, "position": pos1},
331
+ )
332
+ )
333
+
334
+ return issues
335
+
336
+ def _validate_unit_consistency(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
337
+ """Validate unit consistency."""
338
+ issues = []
339
+
340
+ if symbol.units < 1:
341
+ issues.append(
342
+ ValidationIssue(
343
+ category="symbol",
344
+ level="error",
345
+ message=f"Invalid unit count: {symbol.units}",
346
+ context={"symbol": symbol.lib_id},
347
+ )
348
+ )
349
+
350
+ # Check unit names consistency
351
+ if symbol.unit_names:
352
+ for unit_num in symbol.unit_names:
353
+ if unit_num < 1 or unit_num > symbol.units:
354
+ issues.append(
355
+ ValidationIssue(
356
+ category="symbol",
357
+ level="warning",
358
+ message=f"Unit name defined for invalid unit number: {unit_num}",
359
+ context={"symbol": symbol.lib_id, "unit": unit_num},
360
+ )
361
+ )
362
+
363
+ return issues
364
+
365
+ def _validate_extends_format(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
366
+ """Validate extends directive format."""
367
+ issues = []
368
+
369
+ if symbol.extends is not None:
370
+ # Check extends format
371
+ if not symbol.extends.strip():
372
+ issues.append(
373
+ ValidationIssue(
374
+ category="symbol",
375
+ level="error",
376
+ message="Empty extends directive",
377
+ context={"symbol": symbol.lib_id},
378
+ )
379
+ )
380
+
381
+ # Check for self-reference
382
+ if symbol.extends == symbol.name:
383
+ issues.append(
384
+ ValidationIssue(
385
+ category="symbol",
386
+ level="error",
387
+ message="Symbol cannot extend itself",
388
+ context={"symbol": symbol.lib_id},
389
+ )
390
+ )
391
+
392
+ return issues
393
+
394
+ def _validate_pins(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
395
+ """Validate pin definitions."""
396
+ issues = []
397
+
398
+ for pin in symbol.pins:
399
+ # Validate pin number
400
+ if not pin.number:
401
+ issues.append(
402
+ ValidationIssue(
403
+ category="symbol",
404
+ level="error",
405
+ message="Pin missing number",
406
+ context={"symbol": symbol.lib_id},
407
+ )
408
+ )
409
+
410
+ # Validate pin name
411
+ if not pin.name:
412
+ issues.append(
413
+ ValidationIssue(
414
+ category="symbol",
415
+ level="warning",
416
+ message=f"Pin {pin.number} missing name",
417
+ context={"symbol": symbol.lib_id, "pin": pin.number},
418
+ )
419
+ )
420
+
421
+ # Validate pin type
422
+ if not hasattr(pin, "pin_type") or not pin.pin_type:
423
+ issues.append(
424
+ ValidationIssue(
425
+ category="symbol",
426
+ level="warning",
427
+ message=f"Pin {pin.number} missing pin type",
428
+ context={"symbol": symbol.lib_id, "pin": pin.number},
429
+ )
430
+ )
431
+
432
+ return issues
433
+
434
+ def _validate_graphics(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
435
+ """Validate graphic elements."""
436
+ issues = []
437
+
438
+ if not symbol.graphic_elements:
439
+ issues.append(
440
+ ValidationIssue(
441
+ category="symbol",
442
+ level="info",
443
+ message="Symbol has no graphic elements",
444
+ context={"symbol": symbol.lib_id},
445
+ )
446
+ )
447
+
448
+ # Could add more graphic validation here
449
+ # - Check for overlapping elements
450
+ # - Validate coordinate ranges
451
+ # - Check fill/stroke consistency
452
+
453
+ return issues
454
+
455
+ def _validate_units(self, symbol: SymbolDefinition) -> List[ValidationIssue]:
456
+ """Validate unit definitions."""
457
+ issues = []
458
+
459
+ # Check if pins are distributed across units correctly
460
+ if symbol.units > 1:
461
+ unit_pins = {}
462
+ for pin in symbol.pins:
463
+ unit = getattr(pin, "unit", 1)
464
+ if unit not in unit_pins:
465
+ unit_pins[unit] = []
466
+ unit_pins[unit].append(pin)
467
+
468
+ # Check for empty units
469
+ for unit_num in range(1, symbol.units + 1):
470
+ if unit_num not in unit_pins:
471
+ issues.append(
472
+ ValidationIssue(
473
+ category="symbol",
474
+ level="warning",
475
+ message=f"Unit {unit_num} has no pins",
476
+ context={"symbol": symbol.lib_id, "unit": unit_num},
477
+ )
478
+ )
479
+
480
+ return issues
481
+
482
+ def get_validation_summary(self, issues: List[ValidationIssue]) -> Dict[str, Any]:
483
+ """
484
+ Get validation summary statistics.
485
+
486
+ Args:
487
+ issues: List of validation issues
488
+
489
+ Returns:
490
+ Summary dictionary with issue counts and severity
491
+ """
492
+ summary = {
493
+ "total_issues": len(issues),
494
+ "error_count": len([i for i in issues if i.level.value == "error"]),
495
+ "warning_count": len([i for i in issues if i.level.value == "warning"]),
496
+ "info_count": len([i for i in issues if i.level.value == "info"]),
497
+ "severity": (
498
+ "error"
499
+ if any(i.level.value == "error" for i in issues)
500
+ else "warning" if any(i.level.value == "warning" for i in issues) else "info"
501
+ ),
502
+ }
503
+
504
+ return summary