kicad-sch-api 0.4.0__py3-none-any.whl → 0.4.2__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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- 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/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 +142 -47
- 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 +22 -5
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +28 -52
- kicad_sch_api/core/managers/file_io.py +3 -2
- kicad_sch_api/core/managers/metadata.py +6 -5
- kicad_sch_api/core/managers/validation.py +3 -2
- kicad_sch_api/core/managers/wire.py +7 -1
- kicad_sch_api/core/nets.py +38 -43
- kicad_sch_api/core/no_connects.py +29 -53
- kicad_sch_api/core/parser.py +75 -1765
- kicad_sch_api/core/schematic.py +211 -148
- kicad_sch_api/core/texts.py +28 -55
- kicad_sch_api/core/types.py +59 -18
- kicad_sch_api/core/wires.py +27 -75
- 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/utils.py +80 -0
- 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.4.0.dist-info → kicad_sch_api-0.4.2.dist-info}/METADATA +17 -9
- kicad_sch_api-0.4.2.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 -222
- kicad_sch_api/parsers/wire_parser.py +0 -99
- kicad_sch_api-0.4.0.dist-info/RECORD +0 -67
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.2.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.2.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.2.dist-info}/top_level.txt +0 -0
kicad_sch_api/core/labels.py
CHANGED
|
@@ -10,6 +10,7 @@ import uuid
|
|
|
10
10
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
12
|
from ..utils.validation import SchematicValidator, ValidationError, ValidationIssue
|
|
13
|
+
from .collections import BaseCollection
|
|
13
14
|
from .types import Label, Point
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -114,10 +115,13 @@ class LabelElement:
|
|
|
114
115
|
return f"<Label '{self.text}' @ {self.position}>"
|
|
115
116
|
|
|
116
117
|
|
|
117
|
-
class LabelCollection:
|
|
118
|
+
class LabelCollection(BaseCollection[LabelElement]):
|
|
118
119
|
"""
|
|
119
120
|
Collection class for efficient label element management.
|
|
120
121
|
|
|
122
|
+
Inherits from BaseCollection for standard operations and adds label-specific
|
|
123
|
+
functionality including text-based indexing.
|
|
124
|
+
|
|
121
125
|
Provides fast lookup, filtering, and bulk operations for schematic label elements.
|
|
122
126
|
"""
|
|
123
127
|
|
|
@@ -128,18 +132,17 @@ class LabelCollection:
|
|
|
128
132
|
Args:
|
|
129
133
|
labels: Initial list of label data
|
|
130
134
|
"""
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
# Initialize base collection
|
|
136
|
+
super().__init__([], collection_name="labels")
|
|
137
|
+
|
|
138
|
+
# Additional label-specific index
|
|
133
139
|
self._text_index: Dict[str, List[LabelElement]] = {}
|
|
134
|
-
self._modified = False
|
|
135
140
|
|
|
136
141
|
# Add initial labels
|
|
137
142
|
if labels:
|
|
138
143
|
for label_data in labels:
|
|
139
144
|
self._add_to_indexes(LabelElement(label_data, self))
|
|
140
145
|
|
|
141
|
-
logger.debug(f"LabelCollection initialized with {len(self._labels)} labels")
|
|
142
|
-
|
|
143
146
|
def add(
|
|
144
147
|
self,
|
|
145
148
|
text: str,
|
|
@@ -201,9 +204,7 @@ class LabelCollection:
|
|
|
201
204
|
logger.debug(f"Added label: {label_element}")
|
|
202
205
|
return label_element
|
|
203
206
|
|
|
204
|
-
|
|
205
|
-
"""Get label by UUID."""
|
|
206
|
-
return self._uuid_index.get(label_uuid)
|
|
207
|
+
# get() method inherited from BaseCollection
|
|
207
208
|
|
|
208
209
|
def get_by_text(self, text: str) -> List[LabelElement]:
|
|
209
210
|
"""Get all labels with the given text."""
|
|
@@ -219,13 +220,19 @@ class LabelCollection:
|
|
|
219
220
|
Returns:
|
|
220
221
|
True if label was removed, False if not found
|
|
221
222
|
"""
|
|
222
|
-
label_element = self.
|
|
223
|
+
label_element = self.get(label_uuid)
|
|
223
224
|
if not label_element:
|
|
224
225
|
return False
|
|
225
226
|
|
|
226
|
-
# Remove from
|
|
227
|
-
|
|
228
|
-
self.
|
|
227
|
+
# Remove from text index
|
|
228
|
+
text = label_element.text
|
|
229
|
+
if text in self._text_index:
|
|
230
|
+
self._text_index[text].remove(label_element)
|
|
231
|
+
if not self._text_index[text]:
|
|
232
|
+
del self._text_index[text]
|
|
233
|
+
|
|
234
|
+
# Remove using base class method
|
|
235
|
+
super().remove(label_uuid)
|
|
229
236
|
|
|
230
237
|
logger.debug(f"Removed label: {label_element}")
|
|
231
238
|
return True
|
|
@@ -245,7 +252,7 @@ class LabelCollection:
|
|
|
245
252
|
return self._text_index.get(text, []).copy()
|
|
246
253
|
else:
|
|
247
254
|
matches = []
|
|
248
|
-
for label_element in self.
|
|
255
|
+
for label_element in self._items:
|
|
249
256
|
if text.lower() in label_element.text.lower():
|
|
250
257
|
matches.append(label_element)
|
|
251
258
|
return matches
|
|
@@ -260,7 +267,7 @@ class LabelCollection:
|
|
|
260
267
|
Returns:
|
|
261
268
|
List of labels matching predicate
|
|
262
269
|
"""
|
|
263
|
-
return [label for label in self.
|
|
270
|
+
return [label for label in self._items if predicate(label)]
|
|
264
271
|
|
|
265
272
|
def bulk_update(self, criteria: Callable[[LabelElement], bool], updates: Dict[str, Any]):
|
|
266
273
|
"""
|
|
@@ -271,7 +278,7 @@ class LabelCollection:
|
|
|
271
278
|
updates: Dictionary of property updates
|
|
272
279
|
"""
|
|
273
280
|
updated_count = 0
|
|
274
|
-
for label_element in self.
|
|
281
|
+
for label_element in self._items:
|
|
275
282
|
if criteria(label_element):
|
|
276
283
|
for prop, value in updates.items():
|
|
277
284
|
if hasattr(label_element, prop):
|
|
@@ -284,15 +291,12 @@ class LabelCollection:
|
|
|
284
291
|
|
|
285
292
|
def clear(self):
|
|
286
293
|
"""Remove all labels from collection."""
|
|
287
|
-
self._labels.clear()
|
|
288
|
-
self._uuid_index.clear()
|
|
289
294
|
self._text_index.clear()
|
|
290
|
-
|
|
295
|
+
super().clear()
|
|
291
296
|
|
|
292
297
|
def _add_to_indexes(self, label_element: LabelElement):
|
|
293
|
-
"""Add label to internal indexes."""
|
|
294
|
-
self.
|
|
295
|
-
self._uuid_index[label_element.uuid] = label_element
|
|
298
|
+
"""Add label to internal indexes (base + text index)."""
|
|
299
|
+
self._add_item(label_element)
|
|
296
300
|
|
|
297
301
|
# Add to text index
|
|
298
302
|
text = label_element.text
|
|
@@ -300,18 +304,6 @@ class LabelCollection:
|
|
|
300
304
|
self._text_index[text] = []
|
|
301
305
|
self._text_index[text].append(label_element)
|
|
302
306
|
|
|
303
|
-
def _remove_from_indexes(self, label_element: LabelElement):
|
|
304
|
-
"""Remove label from internal indexes."""
|
|
305
|
-
self._labels.remove(label_element)
|
|
306
|
-
del self._uuid_index[label_element.uuid]
|
|
307
|
-
|
|
308
|
-
# Remove from text index
|
|
309
|
-
text = label_element.text
|
|
310
|
-
if text in self._text_index:
|
|
311
|
-
self._text_index[text].remove(label_element)
|
|
312
|
-
if not self._text_index[text]:
|
|
313
|
-
del self._text_index[text]
|
|
314
|
-
|
|
315
307
|
def _update_text_index(self, old_text: str, label_element: LabelElement):
|
|
316
308
|
"""Update text index when label text changes."""
|
|
317
309
|
# Remove from old text index
|
|
@@ -326,23 +318,7 @@ class LabelCollection:
|
|
|
326
318
|
self._text_index[new_text] = []
|
|
327
319
|
self._text_index[new_text].append(label_element)
|
|
328
320
|
|
|
329
|
-
|
|
330
|
-
"""Mark collection as modified."""
|
|
331
|
-
self._modified = True
|
|
332
|
-
|
|
333
|
-
# Collection interface methods
|
|
334
|
-
def __len__(self) -> int:
|
|
335
|
-
"""Return number of labels."""
|
|
336
|
-
return len(self._labels)
|
|
337
|
-
|
|
338
|
-
def __iter__(self) -> Iterator[LabelElement]:
|
|
339
|
-
"""Iterate over labels."""
|
|
340
|
-
return iter(self._labels)
|
|
341
|
-
|
|
342
|
-
def __getitem__(self, index: int) -> LabelElement:
|
|
343
|
-
"""Get label by index."""
|
|
344
|
-
return self._labels[index]
|
|
345
|
-
|
|
321
|
+
# Collection interface methods - __len__, __iter__, __getitem__ inherited from BaseCollection
|
|
346
322
|
def __bool__(self) -> bool:
|
|
347
323
|
"""Return True if collection has labels."""
|
|
348
|
-
return len(self.
|
|
324
|
+
return len(self._items) > 0
|
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any, Dict, Optional, Union
|
|
12
12
|
|
|
13
13
|
from ...utils.validation import ValidationError
|
|
14
|
+
from ..config import config
|
|
14
15
|
from ..formatter import ExactFormatter
|
|
15
16
|
from ..parser import SExpressionParser
|
|
16
17
|
|
|
@@ -219,9 +220,9 @@ class FileIOManager:
|
|
|
219
220
|
return {
|
|
220
221
|
"kicad_sch": {
|
|
221
222
|
"version": 20230819,
|
|
222
|
-
"generator":
|
|
223
|
+
"generator": config.file_format.generator_default,
|
|
223
224
|
"uuid": None, # Will be set by calling code
|
|
224
|
-
"paper":
|
|
225
|
+
"paper": config.paper.default,
|
|
225
226
|
"lib_symbols": {},
|
|
226
227
|
"symbol": [],
|
|
227
228
|
"wire": [],
|
|
@@ -39,10 +39,10 @@ class MetadataManager:
|
|
|
39
39
|
Args:
|
|
40
40
|
paper: Paper size (e.g., "A4", "A3", "Letter", "Legal")
|
|
41
41
|
"""
|
|
42
|
-
|
|
42
|
+
from ..config import config
|
|
43
43
|
|
|
44
|
-
if paper not in valid_sizes:
|
|
45
|
-
logger.warning(f"Unusual paper size: {paper}. Valid sizes: {valid_sizes}")
|
|
44
|
+
if paper not in config.paper.valid_sizes:
|
|
45
|
+
logger.warning(f"Unusual paper size: {paper}. Valid sizes: {config.paper.valid_sizes}")
|
|
46
46
|
|
|
47
47
|
self._data["paper"] = paper
|
|
48
48
|
logger.debug(f"Set paper size: {paper}")
|
|
@@ -260,9 +260,10 @@ class MetadataManager:
|
|
|
260
260
|
issues.append("Title block missing title")
|
|
261
261
|
|
|
262
262
|
# Check paper size
|
|
263
|
+
from ..config import config
|
|
264
|
+
|
|
263
265
|
paper = self.get_paper_size()
|
|
264
|
-
|
|
265
|
-
if paper and paper not in valid_sizes:
|
|
266
|
+
if paper and paper not in config.paper.valid_sizes:
|
|
266
267
|
issues.append(f"Non-standard paper size: {paper}")
|
|
267
268
|
|
|
268
269
|
return issues
|
|
@@ -274,9 +274,10 @@ class ValidationManager:
|
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
# Check paper size
|
|
277
|
+
from ..config import config
|
|
278
|
+
|
|
277
279
|
paper = self._data.get("paper")
|
|
278
|
-
|
|
279
|
-
if paper and paper not in valid_papers:
|
|
280
|
+
if paper and paper not in config.paper.valid_sizes:
|
|
280
281
|
issues.append(
|
|
281
282
|
ValidationIssue(
|
|
282
283
|
category="metadata",
|
|
@@ -305,7 +305,13 @@ class WireManager:
|
|
|
305
305
|
return True
|
|
306
306
|
|
|
307
307
|
# TODO: Implement more sophisticated connectivity analysis
|
|
308
|
-
#
|
|
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
|
|
309
315
|
return False
|
|
310
316
|
|
|
311
317
|
def connect_pins_with_wire(
|
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
|
|
@@ -10,6 +10,7 @@ import uuid
|
|
|
10
10
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
12
|
from ..utils.validation import SchematicValidator, ValidationError, ValidationIssue
|
|
13
|
+
from .collections import BaseCollection
|
|
13
14
|
from .types import NoConnect, Point
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -72,10 +73,13 @@ class NoConnectElement:
|
|
|
72
73
|
return f"<NoConnect @ {self.position}>"
|
|
73
74
|
|
|
74
75
|
|
|
75
|
-
class NoConnectCollection:
|
|
76
|
+
class NoConnectCollection(BaseCollection[NoConnectElement]):
|
|
76
77
|
"""
|
|
77
78
|
Collection class for efficient no-connect element management.
|
|
78
79
|
|
|
80
|
+
Inherits from BaseCollection for standard operations and adds no-connect-specific
|
|
81
|
+
functionality including position-based indexing.
|
|
82
|
+
|
|
79
83
|
Provides fast lookup, filtering, and bulk operations for schematic no-connect elements.
|
|
80
84
|
"""
|
|
81
85
|
|
|
@@ -86,18 +90,17 @@ class NoConnectCollection:
|
|
|
86
90
|
Args:
|
|
87
91
|
no_connects: Initial list of no-connect data
|
|
88
92
|
"""
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
# Initialize base collection
|
|
94
|
+
super().__init__([], collection_name="no_connects")
|
|
95
|
+
|
|
96
|
+
# Additional no-connect-specific index
|
|
91
97
|
self._position_index: Dict[Tuple[float, float], List[NoConnectElement]] = {}
|
|
92
|
-
self._modified = False
|
|
93
98
|
|
|
94
99
|
# Add initial no-connects
|
|
95
100
|
if no_connects:
|
|
96
101
|
for no_connect_data in no_connects:
|
|
97
102
|
self._add_to_indexes(NoConnectElement(no_connect_data, self))
|
|
98
103
|
|
|
99
|
-
logger.debug(f"NoConnectCollection initialized with {len(self._no_connects)} no-connects")
|
|
100
|
-
|
|
101
104
|
def add(
|
|
102
105
|
self,
|
|
103
106
|
position: Union[Point, Tuple[float, float]],
|
|
@@ -144,9 +147,7 @@ class NoConnectCollection:
|
|
|
144
147
|
logger.debug(f"Added no-connect: {no_connect_element}")
|
|
145
148
|
return no_connect_element
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
"""Get no-connect by UUID."""
|
|
149
|
-
return self._uuid_index.get(no_connect_uuid)
|
|
150
|
+
# get() method inherited from BaseCollection
|
|
150
151
|
|
|
151
152
|
def remove(self, no_connect_uuid: str) -> bool:
|
|
152
153
|
"""
|
|
@@ -158,13 +159,19 @@ class NoConnectCollection:
|
|
|
158
159
|
Returns:
|
|
159
160
|
True if no-connect was removed, False if not found
|
|
160
161
|
"""
|
|
161
|
-
no_connect_element = self.
|
|
162
|
+
no_connect_element = self.get(no_connect_uuid)
|
|
162
163
|
if not no_connect_element:
|
|
163
164
|
return False
|
|
164
165
|
|
|
165
|
-
# Remove from
|
|
166
|
-
|
|
167
|
-
self.
|
|
166
|
+
# Remove from position index
|
|
167
|
+
pos_key = (no_connect_element.position.x, no_connect_element.position.y)
|
|
168
|
+
if pos_key in self._position_index:
|
|
169
|
+
self._position_index[pos_key].remove(no_connect_element)
|
|
170
|
+
if not self._position_index[pos_key]:
|
|
171
|
+
del self._position_index[pos_key]
|
|
172
|
+
|
|
173
|
+
# Remove using base class method
|
|
174
|
+
super().remove(no_connect_uuid)
|
|
168
175
|
|
|
169
176
|
logger.debug(f"Removed no-connect: {no_connect_element}")
|
|
170
177
|
return True
|
|
@@ -186,7 +193,7 @@ class NoConnectCollection:
|
|
|
186
193
|
position = Point(position[0], position[1])
|
|
187
194
|
|
|
188
195
|
matches = []
|
|
189
|
-
for no_connect_element in self.
|
|
196
|
+
for no_connect_element in self._items:
|
|
190
197
|
distance = no_connect_element.position.distance_to(position)
|
|
191
198
|
if distance <= tolerance:
|
|
192
199
|
matches.append(no_connect_element)
|
|
@@ -194,7 +201,7 @@ class NoConnectCollection:
|
|
|
194
201
|
|
|
195
202
|
def filter(self, predicate: Callable[[NoConnectElement], bool]) -> List[NoConnectElement]:
|
|
196
203
|
"""
|
|
197
|
-
Filter no-connects by predicate function.
|
|
204
|
+
Filter no-connects by predicate function (delegates to base class find).
|
|
198
205
|
|
|
199
206
|
Args:
|
|
200
207
|
predicate: Function that returns True for no-connects to include
|
|
@@ -202,7 +209,7 @@ class NoConnectCollection:
|
|
|
202
209
|
Returns:
|
|
203
210
|
List of no-connects matching predicate
|
|
204
211
|
"""
|
|
205
|
-
return
|
|
212
|
+
return self.find(predicate)
|
|
206
213
|
|
|
207
214
|
def bulk_update(self, criteria: Callable[[NoConnectElement], bool], updates: Dict[str, Any]):
|
|
208
215
|
"""
|
|
@@ -213,7 +220,7 @@ class NoConnectCollection:
|
|
|
213
220
|
updates: Dictionary of property updates
|
|
214
221
|
"""
|
|
215
222
|
updated_count = 0
|
|
216
|
-
for no_connect_element in self.
|
|
223
|
+
for no_connect_element in self._items:
|
|
217
224
|
if criteria(no_connect_element):
|
|
218
225
|
for prop, value in updates.items():
|
|
219
226
|
if hasattr(no_connect_element, prop):
|
|
@@ -226,15 +233,12 @@ class NoConnectCollection:
|
|
|
226
233
|
|
|
227
234
|
def clear(self):
|
|
228
235
|
"""Remove all no-connects from collection."""
|
|
229
|
-
self._no_connects.clear()
|
|
230
|
-
self._uuid_index.clear()
|
|
231
236
|
self._position_index.clear()
|
|
232
|
-
|
|
237
|
+
super().clear()
|
|
233
238
|
|
|
234
239
|
def _add_to_indexes(self, no_connect_element: NoConnectElement):
|
|
235
|
-
"""Add no-connect to internal indexes."""
|
|
236
|
-
self.
|
|
237
|
-
self._uuid_index[no_connect_element.uuid] = no_connect_element
|
|
240
|
+
"""Add no-connect to internal indexes (base + position index)."""
|
|
241
|
+
self._add_item(no_connect_element)
|
|
238
242
|
|
|
239
243
|
# Add to position index
|
|
240
244
|
pos_key = (no_connect_element.position.x, no_connect_element.position.y)
|
|
@@ -242,35 +246,7 @@ class NoConnectCollection:
|
|
|
242
246
|
self._position_index[pos_key] = []
|
|
243
247
|
self._position_index[pos_key].append(no_connect_element)
|
|
244
248
|
|
|
245
|
-
|
|
246
|
-
"""Remove no-connect from internal indexes."""
|
|
247
|
-
self._no_connects.remove(no_connect_element)
|
|
248
|
-
del self._uuid_index[no_connect_element.uuid]
|
|
249
|
-
|
|
250
|
-
# Remove from position index
|
|
251
|
-
pos_key = (no_connect_element.position.x, no_connect_element.position.y)
|
|
252
|
-
if pos_key in self._position_index:
|
|
253
|
-
self._position_index[pos_key].remove(no_connect_element)
|
|
254
|
-
if not self._position_index[pos_key]:
|
|
255
|
-
del self._position_index[pos_key]
|
|
256
|
-
|
|
257
|
-
def _mark_modified(self):
|
|
258
|
-
"""Mark collection as modified."""
|
|
259
|
-
self._modified = True
|
|
260
|
-
|
|
261
|
-
# Collection interface methods
|
|
262
|
-
def __len__(self) -> int:
|
|
263
|
-
"""Return number of no-connects."""
|
|
264
|
-
return len(self._no_connects)
|
|
265
|
-
|
|
266
|
-
def __iter__(self) -> Iterator[NoConnectElement]:
|
|
267
|
-
"""Iterate over no-connects."""
|
|
268
|
-
return iter(self._no_connects)
|
|
269
|
-
|
|
270
|
-
def __getitem__(self, index: int) -> NoConnectElement:
|
|
271
|
-
"""Get no-connect by index."""
|
|
272
|
-
return self._no_connects[index]
|
|
273
|
-
|
|
249
|
+
# Collection interface methods - __len__, __iter__, __getitem__ inherited from BaseCollection
|
|
274
250
|
def __bool__(self) -> bool:
|
|
275
251
|
"""Return True if collection has no-connects."""
|
|
276
|
-
return len(self.
|
|
252
|
+
return len(self._items) > 0
|