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
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wire Manager for KiCAD schematic wire operations.
|
|
3
|
+
|
|
4
|
+
Handles wire creation, removal, pin connections, and auto-routing functionality
|
|
5
|
+
while managing component pin position calculations and connectivity analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
11
|
+
|
|
12
|
+
from ...library.cache import get_symbol_cache
|
|
13
|
+
from ..types import Point, Wire, WireType
|
|
14
|
+
from ..wires import WireCollection
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WireManager:
|
|
20
|
+
"""
|
|
21
|
+
Manages wire operations and pin connectivity in KiCAD schematics.
|
|
22
|
+
|
|
23
|
+
Responsible for:
|
|
24
|
+
- Wire creation and removal
|
|
25
|
+
- Pin position calculations
|
|
26
|
+
- Auto-routing between pins
|
|
27
|
+
- Connectivity analysis
|
|
28
|
+
- Wire-to-pin connections
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, schematic_data: Dict[str, Any], wire_collection: WireCollection, component_collection
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Initialize WireManager.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
schematic_data: Reference to schematic data
|
|
39
|
+
wire_collection: Wire collection for management
|
|
40
|
+
component_collection: Component collection for pin lookups
|
|
41
|
+
"""
|
|
42
|
+
self._data = schematic_data
|
|
43
|
+
self._wires = wire_collection
|
|
44
|
+
self._components = component_collection
|
|
45
|
+
self._symbol_cache = get_symbol_cache()
|
|
46
|
+
|
|
47
|
+
def add_wire(
|
|
48
|
+
self, start: Union[Point, Tuple[float, float]], end: Union[Point, Tuple[float, float]]
|
|
49
|
+
) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Add a wire connection.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
start: Start point
|
|
55
|
+
end: End point
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
UUID of created wire
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(start, tuple):
|
|
61
|
+
start = Point(start[0], start[1])
|
|
62
|
+
if isinstance(end, tuple):
|
|
63
|
+
end = Point(end[0], end[1])
|
|
64
|
+
|
|
65
|
+
# Use the wire collection to add the wire
|
|
66
|
+
wire_uuid = self._wires.add(start=start, end=end)
|
|
67
|
+
|
|
68
|
+
logger.debug(f"Added wire: {start} -> {end}")
|
|
69
|
+
return wire_uuid
|
|
70
|
+
|
|
71
|
+
def remove_wire(self, wire_uuid: str) -> bool:
|
|
72
|
+
"""
|
|
73
|
+
Remove wire by UUID.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
wire_uuid: UUID of wire to remove
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if wire was removed, False if not found
|
|
80
|
+
"""
|
|
81
|
+
# Remove from wire collection
|
|
82
|
+
removed_from_collection = self._wires.remove(wire_uuid)
|
|
83
|
+
|
|
84
|
+
# Also remove from data structure for consistency
|
|
85
|
+
wires = self._data.get("wires", [])
|
|
86
|
+
removed_from_data = False
|
|
87
|
+
for i, wire in enumerate(wires):
|
|
88
|
+
if wire.get("uuid") == wire_uuid:
|
|
89
|
+
del wires[i]
|
|
90
|
+
removed_from_data = True
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
success = removed_from_collection or removed_from_data
|
|
94
|
+
if success:
|
|
95
|
+
logger.debug(f"Removed wire: {wire_uuid}")
|
|
96
|
+
|
|
97
|
+
return success
|
|
98
|
+
|
|
99
|
+
def add_wire_to_pin(
|
|
100
|
+
self, start: Union[Point, Tuple[float, float]], component_ref: str, pin_number: str
|
|
101
|
+
) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Add wire from a point to a component pin.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
start: Starting point
|
|
107
|
+
component_ref: Component reference (e.g., "R1")
|
|
108
|
+
pin_number: Pin number on component
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
UUID of created wire
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ValueError: If component or pin not found
|
|
115
|
+
"""
|
|
116
|
+
pin_position = self.get_component_pin_position(component_ref, pin_number)
|
|
117
|
+
if pin_position is None:
|
|
118
|
+
raise ValueError(f"Pin {pin_number} not found on component {component_ref}")
|
|
119
|
+
|
|
120
|
+
return self.add_wire(start, pin_position)
|
|
121
|
+
|
|
122
|
+
def add_wire_between_pins(
|
|
123
|
+
self, component1_ref: str, pin1_number: str, component2_ref: str, pin2_number: str
|
|
124
|
+
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Add wire between two component pins.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
component1_ref: First component reference
|
|
130
|
+
pin1_number: First component pin number
|
|
131
|
+
component2_ref: Second component reference
|
|
132
|
+
pin2_number: Second component pin number
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
UUID of created wire
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If components or pins not found
|
|
139
|
+
"""
|
|
140
|
+
pin1_pos = self.get_component_pin_position(component1_ref, pin1_number)
|
|
141
|
+
pin2_pos = self.get_component_pin_position(component2_ref, pin2_number)
|
|
142
|
+
|
|
143
|
+
if pin1_pos is None:
|
|
144
|
+
raise ValueError(f"Pin {pin1_number} not found on component {component1_ref}")
|
|
145
|
+
if pin2_pos is None:
|
|
146
|
+
raise ValueError(f"Pin {pin2_number} not found on component {component2_ref}")
|
|
147
|
+
|
|
148
|
+
return self.add_wire(pin1_pos, pin2_pos)
|
|
149
|
+
|
|
150
|
+
def get_component_pin_position(self, component_ref: str, pin_number: str) -> Optional[Point]:
|
|
151
|
+
"""
|
|
152
|
+
Get absolute position of a component pin.
|
|
153
|
+
|
|
154
|
+
This consolidates the duplicate implementations in the original schematic class.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
component_ref: Component reference (e.g., "R1")
|
|
158
|
+
pin_number: Pin number
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Absolute pin position or None if not found
|
|
162
|
+
"""
|
|
163
|
+
# Find component
|
|
164
|
+
component = self._components.get(component_ref)
|
|
165
|
+
if not component:
|
|
166
|
+
logger.warning(f"Component not found: {component_ref}")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
# Get symbol definition from cache
|
|
170
|
+
symbol_def = self._symbol_cache.get_symbol(component.lib_id)
|
|
171
|
+
if not symbol_def:
|
|
172
|
+
logger.warning(f"Symbol definition not found: {component.lib_id}")
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
# Find pin in symbol definition
|
|
176
|
+
for pin in symbol_def.pins:
|
|
177
|
+
if pin.number == pin_number:
|
|
178
|
+
# Calculate absolute position
|
|
179
|
+
# Apply component rotation/mirroring if needed (simplified for now)
|
|
180
|
+
absolute_x = component.position.x + pin.position.x
|
|
181
|
+
absolute_y = component.position.y + pin.position.y
|
|
182
|
+
|
|
183
|
+
return Point(absolute_x, absolute_y)
|
|
184
|
+
|
|
185
|
+
logger.warning(f"Pin {pin_number} not found on component {component_ref}")
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
def list_component_pins(self, component_ref: str) -> List[Tuple[str, Point]]:
|
|
189
|
+
"""
|
|
190
|
+
List all pins and their positions for a component.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
component_ref: Component reference
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of (pin_number, absolute_position) tuples
|
|
197
|
+
"""
|
|
198
|
+
pins = []
|
|
199
|
+
|
|
200
|
+
# Find component
|
|
201
|
+
component = self._components.get(component_ref)
|
|
202
|
+
if not component:
|
|
203
|
+
return pins
|
|
204
|
+
|
|
205
|
+
# Get symbol definition
|
|
206
|
+
symbol_def = self._symbol_cache.get_symbol(component.lib_id)
|
|
207
|
+
if not symbol_def:
|
|
208
|
+
return pins
|
|
209
|
+
|
|
210
|
+
# Calculate absolute positions for all pins
|
|
211
|
+
for pin in symbol_def.pins:
|
|
212
|
+
absolute_x = component.position.x + pin.position.x
|
|
213
|
+
absolute_y = component.position.y + pin.position.y
|
|
214
|
+
pins.append((pin.number, Point(absolute_x, absolute_y)))
|
|
215
|
+
|
|
216
|
+
return pins
|
|
217
|
+
|
|
218
|
+
def auto_route_pins(
|
|
219
|
+
self,
|
|
220
|
+
component1_ref: str,
|
|
221
|
+
pin1_number: str,
|
|
222
|
+
component2_ref: str,
|
|
223
|
+
pin2_number: str,
|
|
224
|
+
routing_strategy: str = "direct",
|
|
225
|
+
) -> List[str]:
|
|
226
|
+
"""
|
|
227
|
+
Auto-route between two pins with different strategies.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
component1_ref: First component reference
|
|
231
|
+
pin1_number: First component pin number
|
|
232
|
+
component2_ref: Second component reference
|
|
233
|
+
pin2_number: Second component pin number
|
|
234
|
+
routing_strategy: "direct" or "manhattan"
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
List of wire UUIDs created
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
ValueError: If components or pins not found
|
|
241
|
+
"""
|
|
242
|
+
pin1_pos = self.get_component_pin_position(component1_ref, pin1_number)
|
|
243
|
+
pin2_pos = self.get_component_pin_position(component2_ref, pin2_number)
|
|
244
|
+
|
|
245
|
+
if pin1_pos is None:
|
|
246
|
+
raise ValueError(f"Pin {pin1_number} not found on component {component1_ref}")
|
|
247
|
+
if pin2_pos is None:
|
|
248
|
+
raise ValueError(f"Pin {pin2_number} not found on component {component2_ref}")
|
|
249
|
+
|
|
250
|
+
wire_uuids = []
|
|
251
|
+
|
|
252
|
+
if routing_strategy == "direct":
|
|
253
|
+
# Direct wire between pins
|
|
254
|
+
wire_uuid = self.add_wire(pin1_pos, pin2_pos)
|
|
255
|
+
wire_uuids.append(wire_uuid)
|
|
256
|
+
|
|
257
|
+
elif routing_strategy == "manhattan":
|
|
258
|
+
# Manhattan routing (L-shaped path)
|
|
259
|
+
# Route horizontally first, then vertically
|
|
260
|
+
intermediate_point = Point(pin2_pos.x, pin1_pos.y)
|
|
261
|
+
|
|
262
|
+
# Only add intermediate wire if it has length
|
|
263
|
+
if abs(pin1_pos.x - pin2_pos.x) > 0.1: # Minimum wire length
|
|
264
|
+
wire1_uuid = self.add_wire(pin1_pos, intermediate_point)
|
|
265
|
+
wire_uuids.append(wire1_uuid)
|
|
266
|
+
|
|
267
|
+
if abs(pin1_pos.y - pin2_pos.y) > 0.1: # Minimum wire length
|
|
268
|
+
wire2_uuid = self.add_wire(intermediate_point, pin2_pos)
|
|
269
|
+
wire_uuids.append(wire2_uuid)
|
|
270
|
+
|
|
271
|
+
else:
|
|
272
|
+
raise ValueError(f"Unknown routing strategy: {routing_strategy}")
|
|
273
|
+
|
|
274
|
+
logger.info(
|
|
275
|
+
f"Auto-routed {component1_ref}:{pin1_number} to {component2_ref}:{pin2_number} using {routing_strategy}"
|
|
276
|
+
)
|
|
277
|
+
return wire_uuids
|
|
278
|
+
|
|
279
|
+
def are_pins_connected(
|
|
280
|
+
self, component1_ref: str, pin1_number: str, component2_ref: str, pin2_number: str
|
|
281
|
+
) -> bool:
|
|
282
|
+
"""
|
|
283
|
+
Check if two pins are connected via wires.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
component1_ref: First component reference
|
|
287
|
+
pin1_number: First component pin number
|
|
288
|
+
component2_ref: Second component reference
|
|
289
|
+
pin2_number: Second component pin number
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
True if pins are connected, False otherwise
|
|
293
|
+
"""
|
|
294
|
+
pin1_pos = self.get_component_pin_position(component1_ref, pin1_number)
|
|
295
|
+
pin2_pos = self.get_component_pin_position(component2_ref, pin2_number)
|
|
296
|
+
|
|
297
|
+
if pin1_pos is None or pin2_pos is None:
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
# Check for direct wire connection
|
|
301
|
+
for wire in self._wires:
|
|
302
|
+
if (wire.start == pin1_pos and wire.end == pin2_pos) or (
|
|
303
|
+
wire.start == pin2_pos and wire.end == pin1_pos
|
|
304
|
+
):
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
# TODO: Implement more sophisticated connectivity analysis
|
|
308
|
+
# NOTE: Current implementation only checks for direct wire connections between pins.
|
|
309
|
+
# A full implementation would:
|
|
310
|
+
# 1. Follow wire networks through junctions (connection points)
|
|
311
|
+
# 2. Trace through labels (global/hierarchical net connections)
|
|
312
|
+
# 3. Build a complete net connectivity graph
|
|
313
|
+
# This is a known limitation - use ValidationManager for full electrical rule checking.
|
|
314
|
+
# Priority: MEDIUM - Would enable better automated wiring validation
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
def connect_pins_with_wire(
|
|
318
|
+
self, component1_ref: str, pin1_number: str, component2_ref: str, pin2_number: str
|
|
319
|
+
) -> str:
|
|
320
|
+
"""
|
|
321
|
+
Legacy alias for add_wire_between_pins.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
component1_ref: First component reference
|
|
325
|
+
pin1_number: First component pin number
|
|
326
|
+
component2_ref: Second component reference
|
|
327
|
+
pin2_number: Second component pin number
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
UUID of created wire
|
|
331
|
+
"""
|
|
332
|
+
return self.add_wire_between_pins(component1_ref, pin1_number, component2_ref, pin2_number)
|
|
333
|
+
|
|
334
|
+
def get_wire_statistics(self) -> Dict[str, Any]:
|
|
335
|
+
"""
|
|
336
|
+
Get statistics about wires in the schematic.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Dictionary with wire statistics
|
|
340
|
+
"""
|
|
341
|
+
total_wires = len(self._wires)
|
|
342
|
+
total_length = sum(wire.start.distance_to(wire.end) for wire in self._wires)
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
"total_wires": total_wires,
|
|
346
|
+
"total_length": total_length,
|
|
347
|
+
"average_length": total_length / total_wires if total_wires > 0 else 0,
|
|
348
|
+
"wire_types": {
|
|
349
|
+
"normal": len([w for w in self._wires if w.wire_type == WireType.WIRE]),
|
|
350
|
+
"bus": len([w for w in self._wires if w.wire_type == WireType.BUS]),
|
|
351
|
+
},
|
|
352
|
+
}
|
kicad_sch_api/core/nets.py
CHANGED
|
@@ -9,6 +9,7 @@ import logging
|
|
|
9
9
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
|
|
10
10
|
|
|
11
11
|
from ..utils.validation import SchematicValidator, ValidationError, ValidationIssue
|
|
12
|
+
from .collections import BaseCollection
|
|
12
13
|
from .types import Net
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -35,6 +36,11 @@ class NetElement:
|
|
|
35
36
|
self._validator = SchematicValidator()
|
|
36
37
|
|
|
37
38
|
# Core properties with validation
|
|
39
|
+
@property
|
|
40
|
+
def uuid(self) -> str:
|
|
41
|
+
"""Net UUID (uses name as unique identifier)."""
|
|
42
|
+
return self._data.name
|
|
43
|
+
|
|
38
44
|
@property
|
|
39
45
|
def name(self) -> str:
|
|
40
46
|
"""Net name."""
|
|
@@ -47,7 +53,9 @@ class NetElement:
|
|
|
47
53
|
raise ValidationError("Net name cannot be empty")
|
|
48
54
|
old_name = self._data.name
|
|
49
55
|
self._data.name = value.strip()
|
|
56
|
+
# Update name index and rebuild base UUID index since UUID changed
|
|
50
57
|
self._collection._update_name_index(old_name, self)
|
|
58
|
+
self._collection._rebuild_index()
|
|
51
59
|
self._collection._mark_modified()
|
|
52
60
|
|
|
53
61
|
@property
|
|
@@ -117,11 +125,15 @@ class NetElement:
|
|
|
117
125
|
return f"<Net '{self.name}' ({len(self.components)} connections)>"
|
|
118
126
|
|
|
119
127
|
|
|
120
|
-
class NetCollection:
|
|
128
|
+
class NetCollection(BaseCollection[NetElement]):
|
|
121
129
|
"""
|
|
122
130
|
Collection class for efficient net management.
|
|
123
131
|
|
|
132
|
+
Inherits from BaseCollection for standard operations and adds net-specific
|
|
133
|
+
functionality including name-based indexing.
|
|
134
|
+
|
|
124
135
|
Provides fast lookup, filtering, and bulk operations for schematic nets.
|
|
136
|
+
Note: Nets use name as the unique identifier (exposed as .uuid for protocol compatibility).
|
|
125
137
|
"""
|
|
126
138
|
|
|
127
139
|
def __init__(self, nets: List[Net] = None):
|
|
@@ -131,17 +143,17 @@ class NetCollection:
|
|
|
131
143
|
Args:
|
|
132
144
|
nets: Initial list of net data
|
|
133
145
|
"""
|
|
134
|
-
|
|
146
|
+
# Initialize base collection
|
|
147
|
+
super().__init__([], collection_name="nets")
|
|
148
|
+
|
|
149
|
+
# Additional net-specific index (for convenience, duplicates base UUID index)
|
|
135
150
|
self._name_index: Dict[str, NetElement] = {}
|
|
136
|
-
self._modified = False
|
|
137
151
|
|
|
138
152
|
# Add initial nets
|
|
139
153
|
if nets:
|
|
140
154
|
for net_data in nets:
|
|
141
155
|
self._add_to_indexes(NetElement(net_data, self))
|
|
142
156
|
|
|
143
|
-
logger.debug(f"NetCollection initialized with {len(self._nets)} nets")
|
|
144
|
-
|
|
145
157
|
def add(
|
|
146
158
|
self,
|
|
147
159
|
name: str,
|
|
@@ -190,9 +202,11 @@ class NetCollection:
|
|
|
190
202
|
logger.debug(f"Added net: {net_element}")
|
|
191
203
|
return net_element
|
|
192
204
|
|
|
193
|
-
def
|
|
194
|
-
"""Get net by name."""
|
|
195
|
-
return self.
|
|
205
|
+
def get_by_name(self, name: str) -> Optional[NetElement]:
|
|
206
|
+
"""Get net by name (convenience method, equivalent to get(name))."""
|
|
207
|
+
return self.get(name)
|
|
208
|
+
|
|
209
|
+
# get() method inherited from BaseCollection (uses name as UUID)
|
|
196
210
|
|
|
197
211
|
def remove(self, name: str) -> bool:
|
|
198
212
|
"""
|
|
@@ -204,13 +218,16 @@ class NetCollection:
|
|
|
204
218
|
Returns:
|
|
205
219
|
True if net was removed, False if not found
|
|
206
220
|
"""
|
|
207
|
-
net_element = self.
|
|
221
|
+
net_element = self.get(name)
|
|
208
222
|
if not net_element:
|
|
209
223
|
return False
|
|
210
224
|
|
|
211
|
-
# Remove from
|
|
212
|
-
self.
|
|
213
|
-
|
|
225
|
+
# Remove from name index
|
|
226
|
+
if net_element.name in self._name_index:
|
|
227
|
+
del self._name_index[net_element.name]
|
|
228
|
+
|
|
229
|
+
# Remove using base class method
|
|
230
|
+
super().remove(name)
|
|
214
231
|
|
|
215
232
|
logger.debug(f"Removed net: {net_element}")
|
|
216
233
|
return True
|
|
@@ -227,7 +244,7 @@ class NetCollection:
|
|
|
227
244
|
List of matching net elements
|
|
228
245
|
"""
|
|
229
246
|
matches = []
|
|
230
|
-
for net_element in self.
|
|
247
|
+
for net_element in self._items:
|
|
231
248
|
for comp_ref, comp_pin in net_element.components:
|
|
232
249
|
if comp_ref == reference and (pin is None or comp_pin == pin):
|
|
233
250
|
matches.append(net_element)
|
|
@@ -236,7 +253,7 @@ class NetCollection:
|
|
|
236
253
|
|
|
237
254
|
def filter(self, predicate: Callable[[NetElement], bool]) -> List[NetElement]:
|
|
238
255
|
"""
|
|
239
|
-
Filter nets by predicate function.
|
|
256
|
+
Filter nets by predicate function (delegates to base class find).
|
|
240
257
|
|
|
241
258
|
Args:
|
|
242
259
|
predicate: Function that returns True for nets to include
|
|
@@ -244,7 +261,7 @@ class NetCollection:
|
|
|
244
261
|
Returns:
|
|
245
262
|
List of nets matching predicate
|
|
246
263
|
"""
|
|
247
|
-
return
|
|
264
|
+
return self.find(predicate)
|
|
248
265
|
|
|
249
266
|
def bulk_update(self, criteria: Callable[[NetElement], bool], updates: Dict[str, Any]):
|
|
250
267
|
"""
|
|
@@ -255,7 +272,7 @@ class NetCollection:
|
|
|
255
272
|
updates: Dictionary of property updates
|
|
256
273
|
"""
|
|
257
274
|
updated_count = 0
|
|
258
|
-
for net_element in self.
|
|
275
|
+
for net_element in self._items:
|
|
259
276
|
if criteria(net_element):
|
|
260
277
|
for prop, value in updates.items():
|
|
261
278
|
if hasattr(net_element, prop):
|
|
@@ -268,43 +285,21 @@ class NetCollection:
|
|
|
268
285
|
|
|
269
286
|
def clear(self):
|
|
270
287
|
"""Remove all nets from collection."""
|
|
271
|
-
self._nets.clear()
|
|
272
288
|
self._name_index.clear()
|
|
273
|
-
|
|
289
|
+
super().clear()
|
|
274
290
|
|
|
275
291
|
def _add_to_indexes(self, net_element: NetElement):
|
|
276
|
-
"""Add net to internal indexes."""
|
|
277
|
-
self.
|
|
292
|
+
"""Add net to internal indexes (base + name index)."""
|
|
293
|
+
self._add_item(net_element)
|
|
278
294
|
self._name_index[net_element.name] = net_element
|
|
279
295
|
|
|
280
|
-
def _remove_from_indexes(self, net_element: NetElement):
|
|
281
|
-
"""Remove net from internal indexes."""
|
|
282
|
-
self._nets.remove(net_element)
|
|
283
|
-
del self._name_index[net_element.name]
|
|
284
|
-
|
|
285
296
|
def _update_name_index(self, old_name: str, net_element: NetElement):
|
|
286
297
|
"""Update name index when net name changes."""
|
|
287
298
|
if old_name in self._name_index:
|
|
288
299
|
del self._name_index[old_name]
|
|
289
300
|
self._name_index[net_element.name] = net_element
|
|
290
301
|
|
|
291
|
-
|
|
292
|
-
"""Mark collection as modified."""
|
|
293
|
-
self._modified = True
|
|
294
|
-
|
|
295
|
-
# Collection interface methods
|
|
296
|
-
def __len__(self) -> int:
|
|
297
|
-
"""Return number of nets."""
|
|
298
|
-
return len(self._nets)
|
|
299
|
-
|
|
300
|
-
def __iter__(self) -> Iterator[NetElement]:
|
|
301
|
-
"""Iterate over nets."""
|
|
302
|
-
return iter(self._nets)
|
|
303
|
-
|
|
304
|
-
def __getitem__(self, index: int) -> NetElement:
|
|
305
|
-
"""Get net by index."""
|
|
306
|
-
return self._nets[index]
|
|
307
|
-
|
|
302
|
+
# Collection interface methods - __len__, __iter__, __getitem__ inherited from BaseCollection
|
|
308
303
|
def __bool__(self) -> bool:
|
|
309
304
|
"""Return True if collection has nets."""
|
|
310
|
-
return len(self.
|
|
305
|
+
return len(self._items) > 0
|