kicad-sch-api 0.3.4__py3-none-any.whl → 0.4.0__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.

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