kicad-sch-api 0.3.5__py3-none-any.whl → 0.4.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 (81) hide show
  1. kicad_sch_api/__init__.py +2 -2
  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/netlist.py +94 -0
  8. kicad_sch_api/cli/types.py +43 -0
  9. kicad_sch_api/collections/__init__.py +2 -2
  10. kicad_sch_api/collections/base.py +5 -7
  11. kicad_sch_api/collections/components.py +24 -12
  12. kicad_sch_api/collections/junctions.py +31 -43
  13. kicad_sch_api/collections/labels.py +19 -27
  14. kicad_sch_api/collections/wires.py +17 -18
  15. kicad_sch_api/core/collections/__init__.py +5 -0
  16. kicad_sch_api/core/collections/base.py +248 -0
  17. kicad_sch_api/core/component_bounds.py +5 -0
  18. kicad_sch_api/core/components.py +67 -45
  19. kicad_sch_api/core/config.py +85 -3
  20. kicad_sch_api/core/factories/__init__.py +5 -0
  21. kicad_sch_api/core/factories/element_factory.py +276 -0
  22. kicad_sch_api/core/formatter.py +3 -1
  23. kicad_sch_api/core/junctions.py +26 -75
  24. kicad_sch_api/core/labels.py +29 -53
  25. kicad_sch_api/core/managers/__init__.py +26 -0
  26. kicad_sch_api/core/managers/file_io.py +244 -0
  27. kicad_sch_api/core/managers/format_sync.py +501 -0
  28. kicad_sch_api/core/managers/graphics.py +579 -0
  29. kicad_sch_api/core/managers/metadata.py +269 -0
  30. kicad_sch_api/core/managers/sheet.py +454 -0
  31. kicad_sch_api/core/managers/text_elements.py +536 -0
  32. kicad_sch_api/core/managers/validation.py +475 -0
  33. kicad_sch_api/core/managers/wire.py +352 -0
  34. kicad_sch_api/core/nets.py +38 -43
  35. kicad_sch_api/core/no_connects.py +33 -55
  36. kicad_sch_api/core/parser.py +75 -1731
  37. kicad_sch_api/core/schematic.py +951 -1192
  38. kicad_sch_api/core/texts.py +28 -55
  39. kicad_sch_api/core/types.py +60 -22
  40. kicad_sch_api/core/wires.py +27 -75
  41. kicad_sch_api/geometry/font_metrics.py +3 -1
  42. kicad_sch_api/geometry/symbol_bbox.py +40 -21
  43. kicad_sch_api/interfaces/__init__.py +1 -1
  44. kicad_sch_api/interfaces/parser.py +1 -1
  45. kicad_sch_api/interfaces/repository.py +1 -1
  46. kicad_sch_api/interfaces/resolver.py +1 -1
  47. kicad_sch_api/parsers/__init__.py +2 -2
  48. kicad_sch_api/parsers/base.py +7 -10
  49. kicad_sch_api/parsers/elements/__init__.py +22 -0
  50. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  51. kicad_sch_api/parsers/elements/label_parser.py +194 -0
  52. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  53. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  54. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  55. kicad_sch_api/parsers/elements/symbol_parser.py +313 -0
  56. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  57. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  58. kicad_sch_api/parsers/registry.py +4 -2
  59. kicad_sch_api/parsers/utils.py +80 -0
  60. kicad_sch_api/symbols/__init__.py +1 -1
  61. kicad_sch_api/symbols/cache.py +9 -12
  62. kicad_sch_api/symbols/resolver.py +20 -26
  63. kicad_sch_api/symbols/validators.py +188 -137
  64. kicad_sch_api/validation/__init__.py +25 -0
  65. kicad_sch_api/validation/erc.py +171 -0
  66. kicad_sch_api/validation/erc_models.py +203 -0
  67. kicad_sch_api/validation/pin_matrix.py +243 -0
  68. kicad_sch_api/validation/validators.py +391 -0
  69. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
  70. kicad_sch_api-0.4.1.dist-info/RECORD +87 -0
  71. kicad_sch_api/core/manhattan_routing.py +0 -430
  72. kicad_sch_api/core/simple_manhattan.py +0 -228
  73. kicad_sch_api/core/wire_routing.py +0 -380
  74. kicad_sch_api/parsers/label_parser.py +0 -254
  75. kicad_sch_api/parsers/symbol_parser.py +0 -227
  76. kicad_sch_api/parsers/wire_parser.py +0 -99
  77. kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
  78. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
  79. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
  80. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
  81. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/top_level.txt +0 -0
@@ -86,7 +86,7 @@ class SymbolResolver:
86
86
  max_chain_length = 0
87
87
 
88
88
  for symbol in self._inheritance_cache.values():
89
- if hasattr(symbol, '_inheritance_depth'):
89
+ if hasattr(symbol, "_inheritance_depth"):
90
90
  inheritance_chains += 1
91
91
  max_chain_length = max(max_chain_length, symbol._inheritance_depth)
92
92
 
@@ -94,7 +94,7 @@ class SymbolResolver:
94
94
  "resolved_symbols": len(self._inheritance_cache),
95
95
  "inheritance_chains": inheritance_chains,
96
96
  "max_chain_length": max_chain_length,
97
- "cache_size": len(self._inheritance_cache)
97
+ "cache_size": len(self._inheritance_cache),
98
98
  }
99
99
 
100
100
  def _resolve_with_inheritance(self, symbol: SymbolDefinition) -> Optional[SymbolDefinition]:
@@ -112,7 +112,9 @@ class SymbolResolver:
112
112
 
113
113
  # Check for circular inheritance
114
114
  if symbol.lib_id in self._resolution_stack:
115
- logger.error(f"Circular inheritance detected: {' -> '.join(self._resolution_stack + [symbol.lib_id])}")
115
+ logger.error(
116
+ f"Circular inheritance detected: {' -> '.join(self._resolution_stack + [symbol.lib_id])}"
117
+ )
116
118
  return None
117
119
 
118
120
  self._resolution_stack.append(symbol.lib_id)
@@ -136,7 +138,7 @@ class SymbolResolver:
136
138
  resolved_symbol = self._merge_parent_into_child(symbol, resolved_parent)
137
139
 
138
140
  # Track inheritance depth for statistics
139
- parent_depth = getattr(resolved_parent, '_inheritance_depth', 0)
141
+ parent_depth = getattr(resolved_parent, "_inheritance_depth", 0)
140
142
  resolved_symbol._inheritance_depth = parent_depth + 1
141
143
 
142
144
  logger.debug(f"Resolved inheritance: {symbol.lib_id} extends {parent_lib_id}")
@@ -168,9 +170,7 @@ class SymbolResolver:
168
170
  return f"{current_library}:{parent_name}"
169
171
 
170
172
  def _merge_parent_into_child(
171
- self,
172
- child: SymbolDefinition,
173
- parent: SymbolDefinition
173
+ self, child: SymbolDefinition, parent: SymbolDefinition
174
174
  ) -> SymbolDefinition:
175
175
  """
176
176
  Merge parent symbol into child symbol.
@@ -188,10 +188,7 @@ class SymbolResolver:
188
188
  # Merge raw KiCAD data for exact format preservation
189
189
  if child.raw_kicad_data and parent.raw_kicad_data:
190
190
  merged.raw_kicad_data = self._merge_kicad_data(
191
- child.raw_kicad_data,
192
- parent.raw_kicad_data,
193
- child.name,
194
- parent.name
191
+ child.raw_kicad_data, parent.raw_kicad_data, child.name, parent.name
195
192
  )
196
193
 
197
194
  # Merge other properties
@@ -204,11 +201,7 @@ class SymbolResolver:
204
201
  return merged
205
202
 
206
203
  def _merge_kicad_data(
207
- self,
208
- child_data: List,
209
- parent_data: List,
210
- child_name: str,
211
- parent_name: str
204
+ self, child_data: List, parent_data: List, child_name: str, parent_name: str
212
205
  ) -> List:
213
206
  """
214
207
  Merge parent KiCAD data into child KiCAD data.
@@ -227,11 +220,10 @@ class SymbolResolver:
227
220
 
228
221
  # Remove extends directive from child
229
222
  merged = [
230
- item for item in merged
223
+ item
224
+ for item in merged
231
225
  if not (
232
- isinstance(item, list) and
233
- len(item) >= 2 and
234
- item[0] == sexpdata.Symbol("extends")
226
+ isinstance(item, list) and len(item) >= 2 and item[0] == sexpdata.Symbol("extends")
235
227
  )
236
228
  ]
237
229
 
@@ -255,9 +247,7 @@ class SymbolResolver:
255
247
  return merged
256
248
 
257
249
  def _merge_symbol_properties(
258
- self,
259
- child: SymbolDefinition,
260
- parent: SymbolDefinition
250
+ self, child: SymbolDefinition, parent: SymbolDefinition
261
251
  ) -> SymbolDefinition:
262
252
  """
263
253
  Merge symbol properties, with child properties taking precedence.
@@ -315,7 +305,9 @@ class SymbolResolver:
315
305
 
316
306
  def check_symbol(current_lib_id: str) -> None:
317
307
  if current_lib_id in visited:
318
- issues.append(f"Circular inheritance detected: {' -> '.join(chain + [current_lib_id])}")
308
+ issues.append(
309
+ f"Circular inheritance detected: {' -> '.join(chain + [current_lib_id])}"
310
+ )
319
311
  return
320
312
 
321
313
  visited.add(current_lib_id)
@@ -329,7 +321,9 @@ class SymbolResolver:
329
321
  if symbol.extends:
330
322
  parent_lib_id = self._resolve_parent_lib_id(symbol.extends, symbol.library)
331
323
  if not self._cache.has_symbol(parent_lib_id):
332
- issues.append(f"Parent symbol not found: {parent_lib_id} (extended by {current_lib_id})")
324
+ issues.append(
325
+ f"Parent symbol not found: {parent_lib_id} (extended by {current_lib_id})"
326
+ )
333
327
  else:
334
328
  check_symbol(parent_lib_id)
335
329
 
@@ -364,4 +358,4 @@ class SymbolResolver:
364
358
 
365
359
  current_lib_id = self._resolve_parent_lib_id(symbol.extends, symbol.library)
366
360
 
367
- return chain
361
+ return chain
@@ -51,12 +51,14 @@ class SymbolValidator:
51
51
  rule_issues = rule_func(symbol)
52
52
  issues.extend(rule_issues)
53
53
  except Exception as e:
54
- issues.append(ValidationIssue(
55
- category="validation",
56
- message=f"Validation rule '{rule_name}' failed: {e}",
57
- level="error",
58
- context={"symbol": symbol.lib_id, "rule": rule_name}
59
- ))
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
+ )
60
62
 
61
63
  return issues
62
64
 
@@ -99,12 +101,14 @@ class SymbolValidator:
99
101
  return issues
100
102
 
101
103
  if not self._cache:
102
- issues.append(ValidationIssue(
103
- category="inheritance",
104
- message="Cannot validate inheritance without cache",
105
- level="warning",
106
- context={"symbol": symbol.lib_id}
107
- ))
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
+ )
108
112
  return issues
109
113
 
110
114
  # Check for inheritance chain issues
@@ -113,24 +117,28 @@ class SymbolValidator:
113
117
 
114
118
  while current_lib_id:
115
119
  if current_lib_id in visited:
116
- issues.append(ValidationIssue(
117
- category="inheritance",
118
- message=f"Circular inheritance detected in chain starting from {symbol.lib_id}",
119
- level="error",
120
- context={"symbol": symbol.lib_id, "cycle_point": current_lib_id}
121
- ))
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
+ )
122
128
  break
123
129
 
124
130
  visited.add(current_lib_id)
125
131
  current_symbol = self._cache.get_symbol(current_lib_id)
126
132
 
127
133
  if not current_symbol:
128
- issues.append(ValidationIssue(
129
- category="inheritance",
130
- message=f"Missing symbol in inheritance chain: {current_lib_id}",
131
- level="error",
132
- context={"symbol": symbol.lib_id, "missing": current_lib_id}
133
- ))
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
+ )
134
142
  break
135
143
 
136
144
  if not current_symbol.extends:
@@ -145,12 +153,14 @@ class SymbolValidator:
145
153
 
146
154
  # Check if parent exists
147
155
  if not self._cache.has_symbol(current_lib_id):
148
- issues.append(ValidationIssue(
149
- category="inheritance",
150
- message=f"Parent symbol not found: {current_lib_id}",
151
- level="error",
152
- context={"symbol": symbol.lib_id, "parent": current_lib_id}
153
- ))
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
+ )
154
164
  break
155
165
 
156
166
  return issues
@@ -199,12 +209,14 @@ class SymbolValidator:
199
209
  issues = []
200
210
 
201
211
  if not self.validate_lib_id(symbol.lib_id):
202
- issues.append(ValidationIssue(
203
- category="lib_id",
204
- message=f"Invalid lib_id format: {symbol.lib_id}",
205
- level="error",
206
- context={"symbol": symbol.lib_id}
207
- ))
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
+ )
208
220
 
209
221
  return issues
210
222
 
@@ -213,28 +225,34 @@ class SymbolValidator:
213
225
  issues = []
214
226
 
215
227
  if not symbol.name:
216
- issues.append(ValidationIssue(
217
- category="required_fields",
218
- message="Symbol name is required",
219
- level="error",
220
- context={"symbol": symbol.lib_id}
221
- ))
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
+ )
222
236
 
223
237
  if not symbol.library:
224
- issues.append(ValidationIssue(
225
- category="required_fields",
226
- message="Symbol library is required",
227
- level="error",
228
- context={"symbol": symbol.lib_id}
229
- ))
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
+ )
230
246
 
231
247
  if not symbol.reference_prefix:
232
- issues.append(ValidationIssue(
233
- category="required_fields",
234
- message="Symbol reference prefix is missing",
235
- level="warning",
236
- context={"symbol": symbol.lib_id}
237
- ))
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
+ )
238
256
 
239
257
  return issues
240
258
 
@@ -244,23 +262,29 @@ class SymbolValidator:
244
262
 
245
263
  if symbol.reference_prefix:
246
264
  # Check for invalid characters
247
- invalid_chars = set(symbol.reference_prefix) - set("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
265
+ invalid_chars = set(symbol.reference_prefix) - set(
266
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
267
+ )
248
268
  if invalid_chars:
249
- issues.append(ValidationIssue(
250
- category="reference_prefix",
251
- message=f"Reference prefix contains invalid characters: {invalid_chars}",
252
- level="warning",
253
- context={"symbol": symbol.lib_id, "prefix": symbol.reference_prefix}
254
- ))
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
+ )
255
277
 
256
278
  # Check for common patterns
257
279
  if symbol.reference_prefix.lower() in ["u", "ic"] and not symbol.description:
258
- issues.append(ValidationIssue(
259
- category="reference_prefix",
260
- message="Generic IC prefix 'U' - consider adding description",
261
- level="info",
262
- context={"symbol": symbol.lib_id}
263
- ))
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
+ )
264
288
 
265
289
  return issues
266
290
 
@@ -269,12 +293,14 @@ class SymbolValidator:
269
293
  issues = []
270
294
 
271
295
  if not symbol.pins:
272
- issues.append(ValidationIssue(
296
+ issues.append(
297
+ ValidationIssue(
273
298
  category="symbol",
274
299
  level="warning",
275
- message="Symbol has no pins defined",
276
- context={"symbol": symbol.lib_id}
277
- ))
300
+ message="Symbol has no pins defined",
301
+ context={"symbol": symbol.lib_id},
302
+ )
303
+ )
278
304
  return issues
279
305
 
280
306
  # Check for duplicate pin numbers
@@ -282,24 +308,28 @@ class SymbolValidator:
282
308
  duplicates = set([num for num in pin_numbers if pin_numbers.count(num) > 1])
283
309
 
284
310
  if duplicates:
285
- issues.append(ValidationIssue(
311
+ issues.append(
312
+ ValidationIssue(
286
313
  category="symbol",
287
314
  level="error",
288
- message=f"Duplicate pin numbers found: {duplicates}",
289
- context={"symbol": symbol.lib_id, "duplicates": list(duplicates)}
290
- ))
315
+ message=f"Duplicate pin numbers found: {duplicates}",
316
+ context={"symbol": symbol.lib_id, "duplicates": list(duplicates)},
317
+ )
318
+ )
291
319
 
292
320
  # Check for pins with same position
293
321
  pin_positions = [(pin.position.x, pin.position.y) for pin in symbol.pins]
294
322
  for i, pos1 in enumerate(pin_positions):
295
- for j, pos2 in enumerate(pin_positions[i+1:], i+1):
323
+ for j, pos2 in enumerate(pin_positions[i + 1 :], i + 1):
296
324
  if pos1 == pos2:
297
- issues.append(ValidationIssue(
298
- category="symbol",
299
- level="warning",
300
- message=f"Pins at same position: {symbol.pins[i].number} and {symbol.pins[j].number}",
301
- context={"symbol": symbol.lib_id, "position": pos1}
302
- ))
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
+ )
303
333
 
304
334
  return issues
305
335
 
@@ -308,23 +338,27 @@ class SymbolValidator:
308
338
  issues = []
309
339
 
310
340
  if symbol.units < 1:
311
- issues.append(ValidationIssue(
341
+ issues.append(
342
+ ValidationIssue(
312
343
  category="symbol",
313
344
  level="error",
314
- message=f"Invalid unit count: {symbol.units}",
315
- context={"symbol": symbol.lib_id}
316
- ))
345
+ message=f"Invalid unit count: {symbol.units}",
346
+ context={"symbol": symbol.lib_id},
347
+ )
348
+ )
317
349
 
318
350
  # Check unit names consistency
319
351
  if symbol.unit_names:
320
352
  for unit_num in symbol.unit_names:
321
353
  if unit_num < 1 or unit_num > symbol.units:
322
- issues.append(ValidationIssue(
323
- category="symbol",
324
- level="warning",
325
- message=f"Unit name defined for invalid unit number: {unit_num}",
326
- context={"symbol": symbol.lib_id, "unit": unit_num}
327
- ))
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
+ )
328
362
 
329
363
  return issues
330
364
 
@@ -335,21 +369,25 @@ class SymbolValidator:
335
369
  if symbol.extends is not None:
336
370
  # Check extends format
337
371
  if not symbol.extends.strip():
338
- issues.append(ValidationIssue(
339
- category="symbol",
340
- level="error",
341
- message="Empty extends directive",
342
- context={"symbol": symbol.lib_id}
343
- ))
372
+ issues.append(
373
+ ValidationIssue(
374
+ category="symbol",
375
+ level="error",
376
+ message="Empty extends directive",
377
+ context={"symbol": symbol.lib_id},
378
+ )
379
+ )
344
380
 
345
381
  # Check for self-reference
346
382
  if symbol.extends == symbol.name:
347
- issues.append(ValidationIssue(
348
- category="symbol",
349
- level="error",
350
- message="Symbol cannot extend itself",
351
- context={"symbol": symbol.lib_id}
352
- ))
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
+ )
353
391
 
354
392
  return issues
355
393
 
@@ -360,30 +398,36 @@ class SymbolValidator:
360
398
  for pin in symbol.pins:
361
399
  # Validate pin number
362
400
  if not pin.number:
363
- issues.append(ValidationIssue(
364
- category="symbol",
365
- level="error",
366
- message="Pin missing number",
367
- context={"symbol": symbol.lib_id}
368
- ))
401
+ issues.append(
402
+ ValidationIssue(
403
+ category="symbol",
404
+ level="error",
405
+ message="Pin missing number",
406
+ context={"symbol": symbol.lib_id},
407
+ )
408
+ )
369
409
 
370
410
  # Validate pin name
371
411
  if not pin.name:
372
- issues.append(ValidationIssue(
373
- category="symbol",
374
- level="warning",
375
- message=f"Pin {pin.number} missing name",
376
- context={"symbol": symbol.lib_id, "pin": pin.number}
377
- ))
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
+ )
378
420
 
379
421
  # Validate pin type
380
- if not hasattr(pin, 'pin_type') or not pin.pin_type:
381
- issues.append(ValidationIssue(
382
- category="symbol",
383
- level="warning",
384
- message=f"Pin {pin.number} missing pin type",
385
- context={"symbol": symbol.lib_id, "pin": pin.number}
386
- ))
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
+ )
387
431
 
388
432
  return issues
389
433
 
@@ -392,12 +436,14 @@ class SymbolValidator:
392
436
  issues = []
393
437
 
394
438
  if not symbol.graphic_elements:
395
- issues.append(ValidationIssue(
439
+ issues.append(
440
+ ValidationIssue(
396
441
  category="symbol",
397
442
  level="info",
398
- message="Symbol has no graphic elements",
399
- context={"symbol": symbol.lib_id}
400
- ))
443
+ message="Symbol has no graphic elements",
444
+ context={"symbol": symbol.lib_id},
445
+ )
446
+ )
401
447
 
402
448
  # Could add more graphic validation here
403
449
  # - Check for overlapping elements
@@ -414,7 +460,7 @@ class SymbolValidator:
414
460
  if symbol.units > 1:
415
461
  unit_pins = {}
416
462
  for pin in symbol.pins:
417
- unit = getattr(pin, 'unit', 1)
463
+ unit = getattr(pin, "unit", 1)
418
464
  if unit not in unit_pins:
419
465
  unit_pins[unit] = []
420
466
  unit_pins[unit].append(pin)
@@ -422,12 +468,14 @@ class SymbolValidator:
422
468
  # Check for empty units
423
469
  for unit_num in range(1, symbol.units + 1):
424
470
  if unit_num not in unit_pins:
425
- issues.append(ValidationIssue(
426
- category="symbol",
427
- level="warning",
428
- message=f"Unit {unit_num} has no pins",
429
- context={"symbol": symbol.lib_id, "unit": unit_num}
430
- ))
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
+ )
431
479
 
432
480
  return issues
433
481
 
@@ -446,8 +494,11 @@ class SymbolValidator:
446
494
  "error_count": len([i for i in issues if i.level.value == "error"]),
447
495
  "warning_count": len([i for i in issues if i.level.value == "warning"]),
448
496
  "info_count": len([i for i in issues if i.level.value == "info"]),
449
- "severity": "error" if any(i.level.value == "error" for i in issues) else
450
- "warning" if any(i.level.value == "warning" for i in issues) else "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
+ ),
451
502
  }
452
503
 
453
- return summary
504
+ return summary
@@ -0,0 +1,25 @@
1
+ """
2
+ Electrical Rules Check (ERC) validation module.
3
+
4
+ Provides comprehensive electrical validation for KiCAD schematics.
5
+ """
6
+
7
+ from kicad_sch_api.validation.erc import ElectricalRulesChecker
8
+ from kicad_sch_api.validation.erc_models import (
9
+ ERCConfig,
10
+ ERCResult,
11
+ ERCViolation,
12
+ )
13
+ from kicad_sch_api.validation.pin_matrix import (
14
+ PinConflictMatrix,
15
+ PinSeverity,
16
+ )
17
+
18
+ __all__ = [
19
+ "ERCViolation",
20
+ "ERCResult",
21
+ "ERCConfig",
22
+ "PinConflictMatrix",
23
+ "PinSeverity",
24
+ "ElectricalRulesChecker",
25
+ ]