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.
- kicad_sch_api/__init__.py +68 -3
- 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/kicad_to_python.py +169 -0
- kicad_sch_api/cli/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +36 -0
- kicad_sch_api/collections/base.py +604 -0
- kicad_sch_api/collections/components.py +1623 -0
- kicad_sch_api/collections/junctions.py +206 -0
- kicad_sch_api/collections/labels.py +508 -0
- kicad_sch_api/collections/wires.py +292 -0
- kicad_sch_api/core/__init__.py +37 -2
- 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 +34 -7
- kicad_sch_api/core/components.py +213 -52
- kicad_sch_api/core/config.py +110 -15
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +278 -0
- kicad_sch_api/core/formatter.py +60 -9
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +324 -0
- kicad_sch_api/core/managers/__init__.py +30 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +246 -0
- kicad_sch_api/core/managers/format_sync.py +502 -0
- kicad_sch_api/core/managers/graphics.py +580 -0
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +271 -0
- kicad_sch_api/core/managers/sheet.py +492 -0
- kicad_sch_api/core/managers/text_elements.py +537 -0
- kicad_sch_api/core/managers/validation.py +476 -0
- kicad_sch_api/core/managers/wire.py +410 -0
- kicad_sch_api/core/nets.py +305 -0
- kicad_sch_api/core/no_connects.py +252 -0
- kicad_sch_api/core/parser.py +194 -970
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +1328 -1079
- kicad_sch_api/core/texts.py +316 -0
- kicad_sch_api/core/types.py +159 -23
- kicad_sch_api/core/wires.py +27 -75
- kicad_sch_api/exporters/__init__.py +10 -0
- kicad_sch_api/exporters/python_generator.py +610 -0
- kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
- kicad_sch_api/geometry/__init__.py +38 -0
- kicad_sch_api/geometry/font_metrics.py +22 -0
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/geometry/symbol_bbox.py +608 -0
- kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api/parsers/base.py +145 -0
- 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 +216 -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 +485 -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 +155 -0
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api/symbols/validators.py +504 -0
- kicad_sch_api/utils/logging.py +555 -0
- kicad_sch_api/utils/logging_decorators.py +587 -0
- kicad_sch_api/utils/validation.py +16 -22
- 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/wrappers/__init__.py +14 -0
- kicad_sch_api/wrappers/base.py +89 -0
- kicad_sch_api/wrappers/wire.py +198 -0
- kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
- kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
- kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
- mcp_server/__init__.py +34 -0
- mcp_server/example_logging_integration.py +506 -0
- mcp_server/models.py +252 -0
- mcp_server/server.py +357 -0
- mcp_server/tools/__init__.py +32 -0
- mcp_server/tools/component_tools.py +516 -0
- mcp_server/tools/connectivity_tools.py +532 -0
- mcp_server/tools/consolidated_tools.py +1216 -0
- mcp_server/tools/pin_discovery.py +333 -0
- mcp_server/utils/__init__.py +38 -0
- mcp_server/utils/logging.py +127 -0
- mcp_server/utils.py +36 -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-0.3.0.dist-info/METADATA +0 -483
- kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
- kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ERC validators: PinType, Connectivity, Component, Power.
|
|
3
|
+
|
|
4
|
+
Individual validators for different categories of electrical rules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|
9
|
+
|
|
10
|
+
from kicad_sch_api.validation.erc_models import ERCViolation
|
|
11
|
+
from kicad_sch_api.validation.pin_matrix import PinConflictMatrix, PinSeverity
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from kicad_sch_api.core.schematic import Schematic
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseValidator:
|
|
18
|
+
"""Base class for ERC validators."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, schematic: "Schematic") -> None:
|
|
21
|
+
"""Initialize validator.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
schematic: Schematic to validate
|
|
25
|
+
"""
|
|
26
|
+
self.schematic = schematic
|
|
27
|
+
|
|
28
|
+
def validate(self) -> List[ERCViolation]:
|
|
29
|
+
"""Run validation and return violations.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of violations found
|
|
33
|
+
"""
|
|
34
|
+
raise NotImplementedError("Subclasses must implement validate()")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PinTypeValidator(BaseValidator):
|
|
38
|
+
"""Validates pin-to-pin connections for electrical conflicts.
|
|
39
|
+
|
|
40
|
+
Checks all nets for pin type compatibility using the pin conflict matrix.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, schematic: "Schematic", pin_matrix: Optional[PinConflictMatrix] = None) -> None:
|
|
44
|
+
"""Initialize pin type validator.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
schematic: Schematic to validate
|
|
48
|
+
pin_matrix: Optional custom pin conflict matrix
|
|
49
|
+
"""
|
|
50
|
+
super().__init__(schematic)
|
|
51
|
+
self.pin_matrix = pin_matrix or PinConflictMatrix()
|
|
52
|
+
|
|
53
|
+
def validate(self) -> List[ERCViolation]:
|
|
54
|
+
"""Validate pin connections on all nets.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of pin conflict violations
|
|
58
|
+
"""
|
|
59
|
+
violations: List[ERCViolation] = []
|
|
60
|
+
|
|
61
|
+
# Build nets from wires and components
|
|
62
|
+
nets = self._build_nets()
|
|
63
|
+
|
|
64
|
+
# Check each net for pin conflicts
|
|
65
|
+
for net_name, pins in nets.items():
|
|
66
|
+
net_violations = self._check_net_pins(net_name, pins)
|
|
67
|
+
violations.extend(net_violations)
|
|
68
|
+
|
|
69
|
+
return violations
|
|
70
|
+
|
|
71
|
+
def _build_nets(self) -> Dict[str, List[Tuple[str, str, str]]]:
|
|
72
|
+
"""Build net connectivity map.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Dict mapping net name to list of (component_ref, pin_num, pin_type) tuples
|
|
76
|
+
"""
|
|
77
|
+
# TODO: Implement net tracing from wires and components
|
|
78
|
+
# For now, return placeholder
|
|
79
|
+
# This will be implemented when we have full net connectivity analysis
|
|
80
|
+
return {}
|
|
81
|
+
|
|
82
|
+
def _check_net_pins(self, net_name: str, pins: List[Tuple[str, str, str]]) -> List[ERCViolation]:
|
|
83
|
+
"""Check all pin pairs on a net for conflicts.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
net_name: Net name
|
|
87
|
+
pins: List of (component_ref, pin_num, pin_type) tuples
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
List of violations found on this net
|
|
91
|
+
"""
|
|
92
|
+
violations: List[ERCViolation] = []
|
|
93
|
+
|
|
94
|
+
# Check all pairs of pins
|
|
95
|
+
for i, (ref1, pin1_num, pin1_type) in enumerate(pins):
|
|
96
|
+
for ref2, pin2_num, pin2_type in pins[i + 1:]:
|
|
97
|
+
severity = self.pin_matrix.check_connection(pin1_type, pin2_type)
|
|
98
|
+
|
|
99
|
+
if severity == PinSeverity.ERROR:
|
|
100
|
+
violations.append(ERCViolation(
|
|
101
|
+
violation_type="pin_conflict",
|
|
102
|
+
severity="error",
|
|
103
|
+
message=f"Pin conflict: {pin1_type} ({ref1}) connected to {pin2_type} ({ref2})",
|
|
104
|
+
component_refs=[ref1, ref2],
|
|
105
|
+
net_name=net_name,
|
|
106
|
+
pin_numbers=[pin1_num, pin2_num],
|
|
107
|
+
error_code="E001",
|
|
108
|
+
suggested_fix=f"Remove one output or add buffer between {ref1} and {ref2}"
|
|
109
|
+
))
|
|
110
|
+
elif severity == PinSeverity.WARNING:
|
|
111
|
+
violations.append(ERCViolation(
|
|
112
|
+
violation_type="pin_conflict",
|
|
113
|
+
severity="warning",
|
|
114
|
+
message=f"Pin warning: {pin1_type} ({ref1}) connected to {pin2_type} ({ref2})",
|
|
115
|
+
component_refs=[ref1, ref2],
|
|
116
|
+
net_name=net_name,
|
|
117
|
+
pin_numbers=[pin1_num, pin2_num],
|
|
118
|
+
error_code="W005",
|
|
119
|
+
suggested_fix="Verify this connection is intentional"
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
return violations
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ConnectivityValidator(BaseValidator):
|
|
126
|
+
"""Validates wire connectivity and net driving.
|
|
127
|
+
|
|
128
|
+
Checks for dangling wires, unconnected pins, and undriven nets.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def validate(self) -> List[ERCViolation]:
|
|
132
|
+
"""Validate connectivity.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of connectivity violations
|
|
136
|
+
"""
|
|
137
|
+
violations: List[ERCViolation] = []
|
|
138
|
+
|
|
139
|
+
violations.extend(self.find_dangling_wires())
|
|
140
|
+
violations.extend(self.find_unconnected_pins())
|
|
141
|
+
violations.extend(self.find_undriven_nets())
|
|
142
|
+
|
|
143
|
+
return violations
|
|
144
|
+
|
|
145
|
+
def find_dangling_wires(self) -> List[ERCViolation]:
|
|
146
|
+
"""Find wires with only one connection.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of dangling wire violations
|
|
150
|
+
"""
|
|
151
|
+
violations: List[ERCViolation] = []
|
|
152
|
+
|
|
153
|
+
for wire in self.schematic.wires:
|
|
154
|
+
# Check endpoints for connections
|
|
155
|
+
start_connections = self._count_connections_at_point(wire.start)
|
|
156
|
+
end_connections = self._count_connections_at_point(wire.end)
|
|
157
|
+
|
|
158
|
+
if start_connections < 2 or end_connections < 2:
|
|
159
|
+
violations.append(ERCViolation(
|
|
160
|
+
violation_type="dangling_wire",
|
|
161
|
+
severity="warning",
|
|
162
|
+
message=f"Wire has unconnected endpoint at ({wire.start.x}, {wire.start.y})",
|
|
163
|
+
component_refs=[],
|
|
164
|
+
location=wire.start if start_connections < 2 else wire.end,
|
|
165
|
+
error_code="W002",
|
|
166
|
+
suggested_fix="Connect wire to component pin or remove if unused"
|
|
167
|
+
))
|
|
168
|
+
|
|
169
|
+
return violations
|
|
170
|
+
|
|
171
|
+
def find_unconnected_pins(self) -> List[ERCViolation]:
|
|
172
|
+
"""Find input pins with no connections.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List of unconnected pin violations
|
|
176
|
+
"""
|
|
177
|
+
violations: List[ERCViolation] = []
|
|
178
|
+
|
|
179
|
+
for component in self.schematic.components:
|
|
180
|
+
# TODO: Get pin types from symbol library
|
|
181
|
+
# For now, check if any pins have no wires
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
return violations
|
|
185
|
+
|
|
186
|
+
def find_undriven_nets(self) -> List[ERCViolation]:
|
|
187
|
+
"""Find nets with only input pins (no output driver).
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of undriven net violations
|
|
191
|
+
"""
|
|
192
|
+
violations: List[ERCViolation] = []
|
|
193
|
+
|
|
194
|
+
# TODO: Implement net tracing and driver detection
|
|
195
|
+
# This requires full net connectivity analysis
|
|
196
|
+
|
|
197
|
+
return violations
|
|
198
|
+
|
|
199
|
+
def _count_connections_at_point(self, point) -> int:
|
|
200
|
+
"""Count number of connections at a point.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
point: Point to check
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Number of wires/pins at this point
|
|
207
|
+
"""
|
|
208
|
+
# TODO: Implement proper connection counting
|
|
209
|
+
# For now, return 2 (assume connected)
|
|
210
|
+
return 2
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class ComponentValidator(BaseValidator):
|
|
214
|
+
"""Validates component properties and references.
|
|
215
|
+
|
|
216
|
+
Checks for duplicate references, missing values, invalid formats.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
# Valid reference format: Letter(s) followed by number(s)
|
|
220
|
+
REFERENCE_PATTERN = re.compile(r'^[A-Z]+[0-9]+$', re.IGNORECASE)
|
|
221
|
+
|
|
222
|
+
def validate(self) -> List[ERCViolation]:
|
|
223
|
+
"""Validate components.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List of component violations
|
|
227
|
+
"""
|
|
228
|
+
violations: List[ERCViolation] = []
|
|
229
|
+
|
|
230
|
+
violations.extend(self.find_duplicate_references())
|
|
231
|
+
violations.extend(self.validate_component_properties())
|
|
232
|
+
|
|
233
|
+
return violations
|
|
234
|
+
|
|
235
|
+
def find_duplicate_references(self) -> List[ERCViolation]:
|
|
236
|
+
"""Find components with duplicate reference designators.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
List of duplicate reference violations
|
|
240
|
+
"""
|
|
241
|
+
violations: List[ERCViolation] = []
|
|
242
|
+
|
|
243
|
+
# Build reference count map
|
|
244
|
+
ref_to_components: Dict[str, List[str]] = {}
|
|
245
|
+
|
|
246
|
+
for component in self.schematic.components:
|
|
247
|
+
ref = component.reference
|
|
248
|
+
if ref not in ref_to_components:
|
|
249
|
+
ref_to_components[ref] = []
|
|
250
|
+
ref_to_components[ref].append(ref)
|
|
251
|
+
|
|
252
|
+
# Find duplicates
|
|
253
|
+
for ref, components in ref_to_components.items():
|
|
254
|
+
if len(components) > 1:
|
|
255
|
+
violations.append(ERCViolation(
|
|
256
|
+
violation_type="duplicate_reference",
|
|
257
|
+
severity="error",
|
|
258
|
+
message=f"Duplicate reference designator: {ref}",
|
|
259
|
+
component_refs=[ref] * len(components),
|
|
260
|
+
error_code="E004",
|
|
261
|
+
suggested_fix=f"Rename duplicate components (e.g., {ref}, {ref}A, {ref}B)"
|
|
262
|
+
))
|
|
263
|
+
|
|
264
|
+
return violations
|
|
265
|
+
|
|
266
|
+
def validate_component_properties(self) -> List[ERCViolation]:
|
|
267
|
+
"""Validate component properties (value, footprint, etc.).
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
List of property violations
|
|
271
|
+
"""
|
|
272
|
+
violations: List[ERCViolation] = []
|
|
273
|
+
|
|
274
|
+
for component in self.schematic.components:
|
|
275
|
+
# Check for missing value
|
|
276
|
+
if not component.value or component.value.strip() == "":
|
|
277
|
+
violations.append(ERCViolation(
|
|
278
|
+
violation_type="missing_value",
|
|
279
|
+
severity="warning",
|
|
280
|
+
message=f"Component {component.reference} has no value",
|
|
281
|
+
component_refs=[component.reference],
|
|
282
|
+
error_code="W008",
|
|
283
|
+
suggested_fix=f"Add value to {component.reference}"
|
|
284
|
+
))
|
|
285
|
+
|
|
286
|
+
# Check for missing footprint
|
|
287
|
+
if not component.footprint or component.footprint.strip() == "":
|
|
288
|
+
violations.append(ERCViolation(
|
|
289
|
+
violation_type="missing_footprint",
|
|
290
|
+
severity="warning",
|
|
291
|
+
message=f"Component {component.reference} has no footprint",
|
|
292
|
+
component_refs=[component.reference],
|
|
293
|
+
error_code="W007",
|
|
294
|
+
suggested_fix=f"Assign footprint to {component.reference}"
|
|
295
|
+
))
|
|
296
|
+
|
|
297
|
+
# Check reference format
|
|
298
|
+
if not self.REFERENCE_PATTERN.match(component.reference):
|
|
299
|
+
violations.append(ERCViolation(
|
|
300
|
+
violation_type="invalid_reference",
|
|
301
|
+
severity="error",
|
|
302
|
+
message=f"Invalid reference format: {component.reference}",
|
|
303
|
+
component_refs=[component.reference],
|
|
304
|
+
error_code="E005",
|
|
305
|
+
suggested_fix="Use format like R1, U1, C1 (letter + number)"
|
|
306
|
+
))
|
|
307
|
+
|
|
308
|
+
return violations
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class PowerValidator(BaseValidator):
|
|
312
|
+
"""Validates power supply connections.
|
|
313
|
+
|
|
314
|
+
Checks for power flags, power input drivers, and power conflicts.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
# Common power net names
|
|
318
|
+
POWER_NET_NAMES = {
|
|
319
|
+
"VCC", "VDD", "V+", "+5V", "+3V3", "+12V", "+24V",
|
|
320
|
+
"GND", "GNDA", "GNDD", "VSS", "V-",
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
def validate(self) -> List[ERCViolation]:
|
|
324
|
+
"""Validate power connections.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List of power violations
|
|
328
|
+
"""
|
|
329
|
+
violations: List[ERCViolation] = []
|
|
330
|
+
|
|
331
|
+
violations.extend(self.validate_power_flags())
|
|
332
|
+
violations.extend(self.check_power_continuity())
|
|
333
|
+
|
|
334
|
+
return violations
|
|
335
|
+
|
|
336
|
+
def validate_power_flags(self) -> List[ERCViolation]:
|
|
337
|
+
"""Check for missing PWR_FLAG on power nets.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
List of missing power flag violations
|
|
341
|
+
"""
|
|
342
|
+
violations: List[ERCViolation] = []
|
|
343
|
+
|
|
344
|
+
# TODO: Implement power net detection and PWR_FLAG checking
|
|
345
|
+
# This requires:
|
|
346
|
+
# 1. Identify power nets (by name or power input pins)
|
|
347
|
+
# 2. Check for PWR_FLAG symbol or power output on net
|
|
348
|
+
# 3. Generate WARNING (not ERROR per requirements) if missing
|
|
349
|
+
|
|
350
|
+
return violations
|
|
351
|
+
|
|
352
|
+
def check_power_continuity(self) -> List[ERCViolation]:
|
|
353
|
+
"""Check that power inputs are driven by power outputs.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
List of power continuity violations
|
|
357
|
+
"""
|
|
358
|
+
violations: List[ERCViolation] = []
|
|
359
|
+
|
|
360
|
+
# TODO: Implement power driver checking
|
|
361
|
+
# This requires full net tracing with pin type detection
|
|
362
|
+
|
|
363
|
+
return violations
|
|
364
|
+
|
|
365
|
+
def is_power_net(self, net_name: str) -> bool:
|
|
366
|
+
"""Check if net name suggests it's a power net.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
net_name: Net name to check
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
True if likely a power net
|
|
373
|
+
"""
|
|
374
|
+
if not net_name:
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
net_upper = net_name.upper().strip()
|
|
378
|
+
|
|
379
|
+
# Check against known power names
|
|
380
|
+
if net_upper in self.POWER_NET_NAMES:
|
|
381
|
+
return True
|
|
382
|
+
|
|
383
|
+
# Check for common patterns
|
|
384
|
+
if any(pattern in net_upper for pattern in ["VCC", "VDD", "GND", "VSS"]):
|
|
385
|
+
return True
|
|
386
|
+
|
|
387
|
+
# Check for voltage patterns (+5V, +3.3V, etc.)
|
|
388
|
+
if re.match(r'^\+?\d+\.?\d*V$', net_upper):
|
|
389
|
+
return True
|
|
390
|
+
|
|
391
|
+
return False
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wrapper classes for schematic elements.
|
|
3
|
+
|
|
4
|
+
Provides enhanced element access with validation, parent tracking,
|
|
5
|
+
and automatic change notification.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import ElementWrapper
|
|
9
|
+
from .wire import WireWrapper
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ElementWrapper",
|
|
13
|
+
"WireWrapper",
|
|
14
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Base wrapper class for schematic elements."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..collections.base import IndexedCollection
|
|
8
|
+
|
|
9
|
+
# Type variable for the wrapped data type
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ElementWrapper(ABC, Generic[T]):
|
|
14
|
+
"""Base class for all schematic element wrappers.
|
|
15
|
+
|
|
16
|
+
Wrappers enhance raw dataclasses with:
|
|
17
|
+
- Validation on property setters
|
|
18
|
+
- Parent collection tracking for automatic index updates
|
|
19
|
+
- Convenient methods and computed properties
|
|
20
|
+
- Consistent API across different element types
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, data: T, parent_collection: Optional["IndexedCollection[Any]"]):
|
|
24
|
+
"""Initialize the wrapper.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
data: The underlying dataclass instance
|
|
28
|
+
parent_collection: The collection this element belongs to (can be None)
|
|
29
|
+
"""
|
|
30
|
+
self._data = data
|
|
31
|
+
self._collection = parent_collection
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def data(self) -> T:
|
|
35
|
+
"""Get the underlying data object.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The wrapped dataclass instance
|
|
39
|
+
"""
|
|
40
|
+
return self._data
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def uuid(self) -> str:
|
|
45
|
+
"""Get the UUID of the element.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
UUID string
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
def __eq__(self, other: object) -> bool:
|
|
53
|
+
"""Compare wrappers by UUID.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
other: Another wrapper to compare with
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if UUIDs match
|
|
60
|
+
"""
|
|
61
|
+
if not isinstance(other, ElementWrapper):
|
|
62
|
+
return False
|
|
63
|
+
return self.uuid == other.uuid
|
|
64
|
+
|
|
65
|
+
def __hash__(self) -> int:
|
|
66
|
+
"""Hash wrapper by UUID.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Hash of UUID
|
|
70
|
+
"""
|
|
71
|
+
return hash(self.uuid)
|
|
72
|
+
|
|
73
|
+
def __repr__(self) -> str:
|
|
74
|
+
"""Get string representation of wrapper.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
String representation
|
|
78
|
+
"""
|
|
79
|
+
return f"{self.__class__.__name__}({self._data})"
|
|
80
|
+
|
|
81
|
+
def _mark_modified(self) -> None:
|
|
82
|
+
"""Mark the parent collection as modified."""
|
|
83
|
+
if self._collection is not None:
|
|
84
|
+
self._collection._mark_modified()
|
|
85
|
+
|
|
86
|
+
def _invalidate_indexes(self) -> None:
|
|
87
|
+
"""Invalidate parent collection indexes."""
|
|
88
|
+
if self._collection is not None:
|
|
89
|
+
self._collection._dirty_indexes = True
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Wire wrapper class for enhanced wire manipulation."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
4
|
+
|
|
5
|
+
from ..core.types import Point, Wire, WireType
|
|
6
|
+
from .base import ElementWrapper
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..collections.wires import WireCollection
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WireWrapper(ElementWrapper[Wire]):
|
|
13
|
+
"""Enhanced wrapper for Wire with validation and parent tracking.
|
|
14
|
+
|
|
15
|
+
Provides:
|
|
16
|
+
- Validation on property setters (e.g., minimum 2 points for wires)
|
|
17
|
+
- Automatic parent collection notification on changes
|
|
18
|
+
- Convenient access to wire properties
|
|
19
|
+
- Type-safe operations
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, wire: Wire, parent_collection: Optional["WireCollection"] = None):
|
|
23
|
+
"""Initialize wire wrapper.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
wire: The underlying Wire dataclass
|
|
27
|
+
parent_collection: Parent collection for modification tracking (optional)
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(wire, parent_collection)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def uuid(self) -> str:
|
|
33
|
+
"""Get wire UUID.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Wire UUID string
|
|
37
|
+
"""
|
|
38
|
+
return self._data.uuid
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def points(self) -> List[Point]:
|
|
42
|
+
"""Get wire points.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of Point objects defining the wire path
|
|
46
|
+
"""
|
|
47
|
+
return self._data.points
|
|
48
|
+
|
|
49
|
+
@points.setter
|
|
50
|
+
def points(self, value: List[Point]) -> None:
|
|
51
|
+
"""Set wire points with validation.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
value: List of points (must have at least 2 points)
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: If less than 2 points provided
|
|
58
|
+
"""
|
|
59
|
+
if len(value) < 2:
|
|
60
|
+
raise ValueError("Wire must have at least 2 points")
|
|
61
|
+
|
|
62
|
+
# Create new Wire with updated points
|
|
63
|
+
self._data = Wire(
|
|
64
|
+
uuid=self._data.uuid,
|
|
65
|
+
points=value,
|
|
66
|
+
wire_type=self._data.wire_type,
|
|
67
|
+
stroke_width=self._data.stroke_width,
|
|
68
|
+
stroke_type=self._data.stroke_type,
|
|
69
|
+
)
|
|
70
|
+
self._mark_modified()
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def start(self) -> Point:
|
|
74
|
+
"""Get start point (first point of wire).
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
First point in the wire path
|
|
78
|
+
"""
|
|
79
|
+
return self._data.points[0]
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def end(self) -> Point:
|
|
83
|
+
"""Get end point (last point of wire).
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Last point in the wire path
|
|
87
|
+
"""
|
|
88
|
+
return self._data.points[-1]
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def wire_type(self) -> WireType:
|
|
92
|
+
"""Get wire type (WIRE or BUS).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
WireType enum value
|
|
96
|
+
"""
|
|
97
|
+
return self._data.wire_type
|
|
98
|
+
|
|
99
|
+
@wire_type.setter
|
|
100
|
+
def wire_type(self, value: WireType) -> None:
|
|
101
|
+
"""Set wire type.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
value: WireType enum value (WIRE or BUS)
|
|
105
|
+
"""
|
|
106
|
+
self._data = Wire(
|
|
107
|
+
uuid=self._data.uuid,
|
|
108
|
+
points=self._data.points,
|
|
109
|
+
wire_type=value,
|
|
110
|
+
stroke_width=self._data.stroke_width,
|
|
111
|
+
stroke_type=self._data.stroke_type,
|
|
112
|
+
)
|
|
113
|
+
self._mark_modified()
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def stroke_width(self) -> float:
|
|
117
|
+
"""Get stroke width.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Stroke width in mm
|
|
121
|
+
"""
|
|
122
|
+
return self._data.stroke_width
|
|
123
|
+
|
|
124
|
+
@stroke_width.setter
|
|
125
|
+
def stroke_width(self, value: float) -> None:
|
|
126
|
+
"""Set stroke width.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
value: Stroke width in mm
|
|
130
|
+
"""
|
|
131
|
+
self._data = Wire(
|
|
132
|
+
uuid=self._data.uuid,
|
|
133
|
+
points=self._data.points,
|
|
134
|
+
wire_type=self._data.wire_type,
|
|
135
|
+
stroke_width=value,
|
|
136
|
+
stroke_type=self._data.stroke_type,
|
|
137
|
+
)
|
|
138
|
+
self._mark_modified()
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def stroke_type(self) -> str:
|
|
142
|
+
"""Get stroke type.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Stroke type string
|
|
146
|
+
"""
|
|
147
|
+
return self._data.stroke_type
|
|
148
|
+
|
|
149
|
+
@stroke_type.setter
|
|
150
|
+
def stroke_type(self, value: str) -> None:
|
|
151
|
+
"""Set stroke type.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
value: Stroke type string
|
|
155
|
+
"""
|
|
156
|
+
self._data = Wire(
|
|
157
|
+
uuid=self._data.uuid,
|
|
158
|
+
points=self._data.points,
|
|
159
|
+
wire_type=self._data.wire_type,
|
|
160
|
+
stroke_width=self._data.stroke_width,
|
|
161
|
+
stroke_type=value,
|
|
162
|
+
)
|
|
163
|
+
self._mark_modified()
|
|
164
|
+
|
|
165
|
+
# Delegate methods to underlying Wire dataclass
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def length(self) -> float:
|
|
169
|
+
"""Get total wire length.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Total length of all wire segments in mm
|
|
173
|
+
"""
|
|
174
|
+
return self._data.length
|
|
175
|
+
|
|
176
|
+
def is_simple(self) -> bool:
|
|
177
|
+
"""Check if wire is a simple 2-point wire.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if wire has exactly 2 points, False otherwise
|
|
181
|
+
"""
|
|
182
|
+
return self._data.is_simple()
|
|
183
|
+
|
|
184
|
+
def is_horizontal(self) -> bool:
|
|
185
|
+
"""Check if wire is horizontal (delegates to Wire).
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
True if wire is horizontal
|
|
189
|
+
"""
|
|
190
|
+
return self._data.is_horizontal()
|
|
191
|
+
|
|
192
|
+
def is_vertical(self) -> bool:
|
|
193
|
+
"""Check if wire is vertical (delegates to Wire).
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
True if wire is vertical
|
|
197
|
+
"""
|
|
198
|
+
return self._data.is_vertical()
|