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
@@ -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
+ }
@@ -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
- self._nets: List[NetElement] = []
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 get(self, name: str) -> Optional[NetElement]:
194
- """Get net by name."""
195
- return self._name_index.get(name)
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._name_index.get(name)
221
+ net_element = self.get(name)
208
222
  if not net_element:
209
223
  return False
210
224
 
211
- # Remove from indexes
212
- self._remove_from_indexes(net_element)
213
- self._mark_modified()
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._nets:
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 [net for net in self._nets if predicate(net)]
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._nets:
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
- self._mark_modified()
289
+ super().clear()
274
290
 
275
291
  def _add_to_indexes(self, net_element: NetElement):
276
- """Add net to internal indexes."""
277
- self._nets.append(net_element)
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
- def _mark_modified(self):
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._nets) > 0
305
+ return len(self._items) > 0