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,476 @@
1
+ """
2
+ Validation Manager for KiCAD schematic integrity checking.
3
+
4
+ Provides comprehensive validation for schematic integrity, design rules,
5
+ connectivity analysis, and format compliance while collecting and reporting
6
+ validation issues systematically.
7
+ """
8
+
9
+ import logging
10
+ from typing import Any, Dict, List, Optional, Set, Tuple
11
+
12
+ from ...utils.validation import ValidationError, ValidationIssue
13
+ from ..types import Point
14
+ from .base import BaseManager
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ValidationManager(BaseManager):
20
+ """
21
+ Comprehensive validation manager for schematic integrity.
22
+
23
+ Responsible for:
24
+ - Schematic-wide integrity checks
25
+ - Component reference validation
26
+ - Connectivity analysis
27
+ - Design rule checking
28
+ - Format compliance validation
29
+ - Validation issue collection and reporting
30
+ """
31
+
32
+ def __init__(
33
+ self, schematic_data: Dict[str, Any], component_collection=None, wire_collection=None
34
+ ):
35
+ """
36
+ Initialize ValidationManager.
37
+
38
+ Args:
39
+ schematic_data: Reference to schematic data
40
+ component_collection: Component collection for validation
41
+ wire_collection: Wire collection for connectivity analysis
42
+ """
43
+ super().__init__(schematic_data)
44
+ self._components = component_collection
45
+ self._wires = wire_collection
46
+ self._validation_rules = self._initialize_validation_rules()
47
+
48
+ def validate_schematic(self) -> List[ValidationIssue]:
49
+ """
50
+ Perform comprehensive schematic validation.
51
+
52
+ Returns:
53
+ List of all validation issues found
54
+ """
55
+ issues = []
56
+
57
+ # Run all validation rules
58
+ for rule_name, rule_func in self._validation_rules.items():
59
+ try:
60
+ rule_issues = rule_func()
61
+ issues.extend(rule_issues)
62
+ logger.debug(f"Validation rule '{rule_name}' found {len(rule_issues)} issues")
63
+ except Exception as e:
64
+ issues.append(
65
+ ValidationIssue(
66
+ category="validation_system",
67
+ message=f"Validation rule '{rule_name}' failed: {e}",
68
+ level="error",
69
+ context={"rule": rule_name, "error": str(e)},
70
+ )
71
+ )
72
+ logger.error(f"Validation rule '{rule_name}' failed: {e}")
73
+
74
+ logger.info(f"Schematic validation completed with {len(issues)} issues")
75
+ return issues
76
+
77
+ def validate_component_references(self) -> List[ValidationIssue]:
78
+ """
79
+ Validate component references for duplicates and format.
80
+
81
+ Returns:
82
+ List of reference validation issues
83
+ """
84
+ issues = []
85
+
86
+ if not self._components:
87
+ return issues
88
+
89
+ references = []
90
+ reference_positions = {}
91
+
92
+ # Collect all component references
93
+ for component in self._components:
94
+ ref = component.reference
95
+ references.append(ref)
96
+ reference_positions[ref] = component.position
97
+
98
+ # Check for duplicate references
99
+ duplicates = set([ref for ref in references if references.count(ref) > 1])
100
+ for duplicate_ref in duplicates:
101
+ issues.append(
102
+ ValidationIssue(
103
+ category="component_references",
104
+ message=f"Duplicate component reference: {duplicate_ref}",
105
+ level="error",
106
+ context={"reference": duplicate_ref},
107
+ )
108
+ )
109
+
110
+ # Check reference format
111
+ for ref in set(references):
112
+ if not self._validate_reference_format(ref):
113
+ issues.append(
114
+ ValidationIssue(
115
+ category="component_references",
116
+ message=f"Invalid reference format: {ref}",
117
+ level="warning",
118
+ context={"reference": ref},
119
+ )
120
+ )
121
+
122
+ return issues
123
+
124
+ def validate_connectivity(self) -> List[ValidationIssue]:
125
+ """
126
+ Validate electrical connectivity and nets.
127
+
128
+ Returns:
129
+ List of connectivity validation issues
130
+ """
131
+ issues = []
132
+
133
+ if not self._wires or not self._components:
134
+ return issues
135
+
136
+ # Check for unconnected pins
137
+ unconnected_pins = self._find_unconnected_pins()
138
+ for component_ref, pin_number in unconnected_pins:
139
+ issues.append(
140
+ ValidationIssue(
141
+ category="connectivity",
142
+ message=f"Unconnected pin: {component_ref}.{pin_number}",
143
+ level="warning",
144
+ context={"component": component_ref, "pin": pin_number},
145
+ )
146
+ )
147
+
148
+ # Check for floating wires
149
+ floating_wires = self._find_floating_wires()
150
+ for wire_uuid in floating_wires:
151
+ issues.append(
152
+ ValidationIssue(
153
+ category="connectivity",
154
+ message=f"Floating wire (not connected to components): {wire_uuid}",
155
+ level="warning",
156
+ context={"wire": wire_uuid},
157
+ )
158
+ )
159
+
160
+ # Check for short circuits
161
+ short_circuits = self._find_potential_short_circuits()
162
+ for circuit_info in short_circuits:
163
+ issues.append(
164
+ ValidationIssue(
165
+ category="connectivity",
166
+ message=f"Potential short circuit: {circuit_info['description']}",
167
+ level="error",
168
+ context=circuit_info,
169
+ )
170
+ )
171
+
172
+ return issues
173
+
174
+ def validate_positioning(self) -> List[ValidationIssue]:
175
+ """
176
+ Validate component and element positioning.
177
+
178
+ Returns:
179
+ List of positioning validation issues
180
+ """
181
+ issues = []
182
+
183
+ # Check for overlapping components
184
+ if self._components:
185
+ overlapping_components = self._find_overlapping_components()
186
+ for comp1_ref, comp2_ref, distance in overlapping_components:
187
+ issues.append(
188
+ ValidationIssue(
189
+ category="positioning",
190
+ message=f"Components too close: {comp1_ref} and {comp2_ref} (distance: {distance:.2f})",
191
+ level="warning",
192
+ context={
193
+ "component1": comp1_ref,
194
+ "component2": comp2_ref,
195
+ "distance": distance,
196
+ },
197
+ )
198
+ )
199
+
200
+ # Check for components outside typical bounds
201
+ if self._components:
202
+ out_of_bounds = self._find_components_out_of_bounds()
203
+ for component_ref, position in out_of_bounds:
204
+ issues.append(
205
+ ValidationIssue(
206
+ category="positioning",
207
+ message=f"Component outside typical bounds: {component_ref} at {position}",
208
+ level="info",
209
+ context={"component": component_ref, "position": (position.x, position.y)},
210
+ )
211
+ )
212
+
213
+ return issues
214
+
215
+ def validate_design_rules(self) -> List[ValidationIssue]:
216
+ """
217
+ Validate against design rules and best practices.
218
+
219
+ Returns:
220
+ List of design rule validation issues
221
+ """
222
+ issues = []
223
+
224
+ # Check minimum wire spacing
225
+ wire_spacing_issues = self._check_wire_spacing()
226
+ issues.extend(wire_spacing_issues)
227
+
228
+ # Check power and ground connections
229
+ power_issues = self._check_power_connections()
230
+ issues.extend(power_issues)
231
+
232
+ # Check for missing bypass capacitors (simplified check)
233
+ bypass_issues = self._check_bypass_capacitors()
234
+ issues.extend(bypass_issues)
235
+
236
+ return issues
237
+
238
+ def validate_metadata(self) -> List[ValidationIssue]:
239
+ """
240
+ Validate schematic metadata and structure.
241
+
242
+ Returns:
243
+ List of metadata validation issues
244
+ """
245
+ issues = []
246
+
247
+ # Check required metadata fields
248
+ if not self._data.get("version"):
249
+ issues.append(
250
+ ValidationIssue(
251
+ category="metadata",
252
+ message="Missing KiCAD version information",
253
+ level="warning",
254
+ context={},
255
+ )
256
+ )
257
+
258
+ if not self._data.get("generator"):
259
+ issues.append(
260
+ ValidationIssue(
261
+ category="metadata",
262
+ message="Missing generator information",
263
+ level="info",
264
+ context={},
265
+ )
266
+ )
267
+
268
+ # Check title block
269
+ title_block = self._data.get("title_block", {})
270
+ if not title_block.get("title"):
271
+ issues.append(
272
+ ValidationIssue(
273
+ category="metadata", message="Missing schematic title", level="info", context={}
274
+ )
275
+ )
276
+
277
+ # Check paper size
278
+ from ..config import config
279
+
280
+ paper = self._data.get("paper")
281
+ if paper and paper not in config.paper.valid_sizes:
282
+ issues.append(
283
+ ValidationIssue(
284
+ category="metadata",
285
+ message=f"Non-standard paper size: {paper}",
286
+ level="info",
287
+ context={"paper": paper},
288
+ )
289
+ )
290
+
291
+ return issues
292
+
293
+ def get_validation_summary(self, issues: List[ValidationIssue]) -> Dict[str, Any]:
294
+ """
295
+ Generate validation summary statistics.
296
+
297
+ Args:
298
+ issues: List of validation issues
299
+
300
+ Returns:
301
+ Summary dictionary with counts and severity
302
+ """
303
+ summary = {
304
+ "total_issues": len(issues),
305
+ "error_count": len([i for i in issues if i.level == "error"]),
306
+ "warning_count": len([i for i in issues if i.level == "warning"]),
307
+ "info_count": len([i for i in issues if i.level == "info"]),
308
+ "categories": {},
309
+ "severity": "info",
310
+ }
311
+
312
+ # Count by category
313
+ for issue in issues:
314
+ category = issue.category
315
+ if category not in summary["categories"]:
316
+ summary["categories"][category] = 0
317
+ summary["categories"][category] += 1
318
+
319
+ # Determine overall severity
320
+ if summary["error_count"] > 0:
321
+ summary["severity"] = "error"
322
+ elif summary["warning_count"] > 0:
323
+ summary["severity"] = "warning"
324
+
325
+ return summary
326
+
327
+ def _initialize_validation_rules(self) -> Dict[str, callable]:
328
+ """Initialize all validation rules."""
329
+ return {
330
+ "component_references": self.validate_component_references,
331
+ "connectivity": self.validate_connectivity,
332
+ "positioning": self.validate_positioning,
333
+ "design_rules": self.validate_design_rules,
334
+ "metadata": self.validate_metadata,
335
+ }
336
+
337
+ def _validate_reference_format(self, reference: str) -> bool:
338
+ """Validate component reference format."""
339
+ if not reference:
340
+ return False
341
+
342
+ # Must start with letter(s), followed by numbers
343
+ if not reference[0].isalpha():
344
+ return False
345
+
346
+ # Find where numbers start
347
+ alpha_end = 0
348
+ for i, char in enumerate(reference):
349
+ if char.isdigit():
350
+ alpha_end = i
351
+ break
352
+ else:
353
+ return False # No numbers found
354
+
355
+ # Check alpha part
356
+ alpha_part = reference[:alpha_end]
357
+ if not alpha_part.isalpha():
358
+ return False
359
+
360
+ # Check numeric part
361
+ numeric_part = reference[alpha_end:]
362
+ if not numeric_part.isdigit():
363
+ return False
364
+
365
+ return True
366
+
367
+ def _find_unconnected_pins(self) -> List[Tuple[str, str]]:
368
+ """Find component pins that are not connected to any wires."""
369
+ unconnected = []
370
+
371
+ if not self._components or not self._wires:
372
+ return unconnected
373
+
374
+ # Get all wire endpoints
375
+ wire_points = set()
376
+ for wire in self._wires:
377
+ wire_points.add((wire.start.x, wire.start.y))
378
+ wire_points.add((wire.end.x, wire.end.y))
379
+
380
+ # Check each component pin
381
+ for component in self._components:
382
+ # This would need actual pin position calculation
383
+ # Simplified for now - would use component's pin positions
384
+ pass
385
+
386
+ return unconnected
387
+
388
+ def _find_floating_wires(self) -> List[str]:
389
+ """Find wires that don't connect to any components."""
390
+ floating = []
391
+
392
+ if not self._wires or not self._components:
393
+ return floating
394
+
395
+ # This would need actual connectivity analysis
396
+ # Simplified implementation
397
+ return floating
398
+
399
+ def _find_potential_short_circuits(self) -> List[Dict[str, Any]]:
400
+ """Find potential short circuits in the design."""
401
+ short_circuits = []
402
+
403
+ # This would need sophisticated electrical analysis
404
+ # Simplified implementation
405
+ return short_circuits
406
+
407
+ def _find_overlapping_components(self) -> List[Tuple[str, str, float]]:
408
+ """Find components that are positioned too close together."""
409
+ overlapping = []
410
+
411
+ if not self._components:
412
+ return overlapping
413
+
414
+ components = list(self._components)
415
+ min_distance = 10.0 # Minimum distance threshold
416
+
417
+ for i, comp1 in enumerate(components):
418
+ for comp2 in components[i + 1 :]:
419
+ distance = comp1.position.distance_to(comp2.position)
420
+ if distance < min_distance:
421
+ overlapping.append((comp1.reference, comp2.reference, distance))
422
+
423
+ return overlapping
424
+
425
+ def _find_components_out_of_bounds(self) -> List[Tuple[str, Point]]:
426
+ """Find components positioned outside typical schematic bounds."""
427
+ out_of_bounds = []
428
+
429
+ if not self._components:
430
+ return out_of_bounds
431
+
432
+ # Define typical bounds (these could be configurable)
433
+ min_x, min_y = 0, 0
434
+ max_x, max_y = 1000, 1000 # Adjust based on paper size
435
+
436
+ for component in self._components:
437
+ pos = component.position
438
+ if pos.x < min_x or pos.x > max_x or pos.y < min_y or pos.y > max_y:
439
+ out_of_bounds.append((component.reference, pos))
440
+
441
+ return out_of_bounds
442
+
443
+ def _check_wire_spacing(self) -> List[ValidationIssue]:
444
+ """Check minimum wire spacing requirements."""
445
+ issues = []
446
+
447
+ if not self._wires:
448
+ return issues
449
+
450
+ # This would check wire-to-wire spacing
451
+ # Simplified implementation
452
+ return issues
453
+
454
+ def _check_power_connections(self) -> List[ValidationIssue]:
455
+ """Check power and ground connection integrity."""
456
+ issues = []
457
+
458
+ if not self._components:
459
+ return issues
460
+
461
+ # Look for power components without proper connections
462
+ # This would need symbol analysis to identify power pins
463
+ # Simplified implementation
464
+ return issues
465
+
466
+ def _check_bypass_capacitors(self) -> List[ValidationIssue]:
467
+ """Check for missing bypass capacitors near ICs."""
468
+ issues = []
469
+
470
+ if not self._components:
471
+ return issues
472
+
473
+ # Find ICs and check for nearby capacitors
474
+ # This would need symbol analysis and proximity checking
475
+ # Simplified implementation
476
+ return issues