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.
- kicad_sch_api/__init__.py +2 -2
- kicad_sch_api/cli/__init__.py +45 -0
- kicad_sch_api/cli/base.py +302 -0
- kicad_sch_api/cli/bom.py +164 -0
- kicad_sch_api/cli/erc.py +229 -0
- kicad_sch_api/cli/export_docs.py +289 -0
- kicad_sch_api/cli/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +2 -2
- kicad_sch_api/collections/base.py +5 -7
- kicad_sch_api/collections/components.py +24 -12
- kicad_sch_api/collections/junctions.py +31 -43
- kicad_sch_api/collections/labels.py +19 -27
- kicad_sch_api/collections/wires.py +17 -18
- kicad_sch_api/core/collections/__init__.py +5 -0
- kicad_sch_api/core/collections/base.py +248 -0
- kicad_sch_api/core/component_bounds.py +5 -0
- kicad_sch_api/core/components.py +67 -45
- kicad_sch_api/core/config.py +85 -3
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +276 -0
- kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +29 -53
- kicad_sch_api/core/managers/__init__.py +26 -0
- kicad_sch_api/core/managers/file_io.py +244 -0
- kicad_sch_api/core/managers/format_sync.py +501 -0
- kicad_sch_api/core/managers/graphics.py +579 -0
- kicad_sch_api/core/managers/metadata.py +269 -0
- kicad_sch_api/core/managers/sheet.py +454 -0
- kicad_sch_api/core/managers/text_elements.py +536 -0
- kicad_sch_api/core/managers/validation.py +475 -0
- kicad_sch_api/core/managers/wire.py +352 -0
- kicad_sch_api/core/nets.py +38 -43
- kicad_sch_api/core/no_connects.py +33 -55
- kicad_sch_api/core/parser.py +75 -1731
- kicad_sch_api/core/schematic.py +951 -1192
- kicad_sch_api/core/texts.py +28 -55
- kicad_sch_api/core/types.py +60 -22
- kicad_sch_api/core/wires.py +27 -75
- kicad_sch_api/geometry/font_metrics.py +3 -1
- kicad_sch_api/geometry/symbol_bbox.py +40 -21
- kicad_sch_api/interfaces/__init__.py +1 -1
- kicad_sch_api/interfaces/parser.py +1 -1
- kicad_sch_api/interfaces/repository.py +1 -1
- kicad_sch_api/interfaces/resolver.py +1 -1
- kicad_sch_api/parsers/__init__.py +2 -2
- kicad_sch_api/parsers/base.py +7 -10
- kicad_sch_api/parsers/elements/__init__.py +22 -0
- kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
- kicad_sch_api/parsers/elements/label_parser.py +194 -0
- kicad_sch_api/parsers/elements/library_parser.py +165 -0
- kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
- kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
- kicad_sch_api/parsers/elements/symbol_parser.py +313 -0
- kicad_sch_api/parsers/elements/text_parser.py +250 -0
- kicad_sch_api/parsers/elements/wire_parser.py +242 -0
- kicad_sch_api/parsers/registry.py +4 -2
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +1 -1
- kicad_sch_api/symbols/cache.py +9 -12
- kicad_sch_api/symbols/resolver.py +20 -26
- kicad_sch_api/symbols/validators.py +188 -137
- kicad_sch_api/validation/__init__.py +25 -0
- kicad_sch_api/validation/erc.py +171 -0
- kicad_sch_api/validation/erc_models.py +203 -0
- kicad_sch_api/validation/pin_matrix.py +243 -0
- kicad_sch_api/validation/validators.py +391 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
- kicad_sch_api-0.4.1.dist-info/RECORD +87 -0
- kicad_sch_api/core/manhattan_routing.py +0 -430
- kicad_sch_api/core/simple_manhattan.py +0 -228
- kicad_sch_api/core/wire_routing.py +0 -380
- kicad_sch_api/parsers/label_parser.py +0 -254
- kicad_sch_api/parsers/symbol_parser.py +0 -227
- kicad_sch_api/parsers/wire_parser.py +0 -99
- kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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(
|
|
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,
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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(
|
|
265
|
+
invalid_chars = set(symbol.reference_prefix) - set(
|
|
266
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
|
267
|
+
)
|
|
248
268
|
if invalid_chars:
|
|
249
|
-
issues.append(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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(
|
|
296
|
+
issues.append(
|
|
297
|
+
ValidationIssue(
|
|
273
298
|
category="symbol",
|
|
274
299
|
level="warning",
|
|
275
|
-
|
|
276
|
-
|
|
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(
|
|
311
|
+
issues.append(
|
|
312
|
+
ValidationIssue(
|
|
286
313
|
category="symbol",
|
|
287
314
|
level="error",
|
|
288
|
-
|
|
289
|
-
|
|
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(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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(
|
|
341
|
+
issues.append(
|
|
342
|
+
ValidationIssue(
|
|
312
343
|
category="symbol",
|
|
313
344
|
level="error",
|
|
314
|
-
|
|
315
|
-
|
|
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(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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,
|
|
381
|
-
issues.append(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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(
|
|
439
|
+
issues.append(
|
|
440
|
+
ValidationIssue(
|
|
396
441
|
category="symbol",
|
|
397
442
|
level="info",
|
|
398
|
-
|
|
399
|
-
|
|
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,
|
|
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(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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":
|
|
450
|
-
|
|
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
|
+
]
|