kicad-sch-api 0.4.0__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/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 +62 -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/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.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 -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.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Element Factory for creating schematic elements from dictionaries.
|
|
3
|
+
|
|
4
|
+
Centralizes object creation logic that was previously duplicated in Schematic.__init__.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from ..types import (
|
|
11
|
+
HierarchicalLabelShape,
|
|
12
|
+
Junction,
|
|
13
|
+
Label,
|
|
14
|
+
LabelType,
|
|
15
|
+
Net,
|
|
16
|
+
NoConnect,
|
|
17
|
+
Point,
|
|
18
|
+
Text,
|
|
19
|
+
Wire,
|
|
20
|
+
WireType,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def point_from_dict_or_tuple(position: Any) -> Point:
|
|
25
|
+
"""Convert position data (dict or tuple) to Point object."""
|
|
26
|
+
if isinstance(position, dict):
|
|
27
|
+
return Point(position.get("x", 0), position.get("y", 0))
|
|
28
|
+
elif isinstance(position, (list, tuple)):
|
|
29
|
+
return Point(position[0], position[1])
|
|
30
|
+
elif isinstance(position, Point):
|
|
31
|
+
return position
|
|
32
|
+
else:
|
|
33
|
+
return Point(0, 0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ElementFactory:
|
|
37
|
+
"""Factory for creating schematic elements from dictionary data."""
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def create_wire(wire_dict: Dict[str, Any]) -> Wire:
|
|
41
|
+
"""
|
|
42
|
+
Create Wire object from dictionary.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
wire_dict: Dictionary containing wire data
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Wire object
|
|
49
|
+
"""
|
|
50
|
+
points = []
|
|
51
|
+
for point_data in wire_dict.get("points", []):
|
|
52
|
+
if isinstance(point_data, dict):
|
|
53
|
+
points.append(Point(point_data["x"], point_data["y"]))
|
|
54
|
+
elif isinstance(point_data, (list, tuple)):
|
|
55
|
+
points.append(Point(point_data[0], point_data[1]))
|
|
56
|
+
else:
|
|
57
|
+
points.append(point_data)
|
|
58
|
+
|
|
59
|
+
return Wire(
|
|
60
|
+
uuid=wire_dict.get("uuid", str(uuid.uuid4())),
|
|
61
|
+
points=points,
|
|
62
|
+
wire_type=WireType(wire_dict.get("wire_type", "wire")),
|
|
63
|
+
stroke_width=wire_dict.get("stroke_width", 0.0),
|
|
64
|
+
stroke_type=wire_dict.get("stroke_type", "default"),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def create_junction(junction_dict: Dict[str, Any]) -> Junction:
|
|
69
|
+
"""
|
|
70
|
+
Create Junction object from dictionary.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
junction_dict: Dictionary containing junction data
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Junction object
|
|
77
|
+
"""
|
|
78
|
+
position = junction_dict.get("position", {"x": 0, "y": 0})
|
|
79
|
+
pos = point_from_dict_or_tuple(position)
|
|
80
|
+
|
|
81
|
+
return Junction(
|
|
82
|
+
uuid=junction_dict.get("uuid", str(uuid.uuid4())),
|
|
83
|
+
position=pos,
|
|
84
|
+
diameter=junction_dict.get("diameter", 0),
|
|
85
|
+
color=junction_dict.get("color", (0, 0, 0, 0)),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def create_text(text_dict: Dict[str, Any]) -> Text:
|
|
90
|
+
"""
|
|
91
|
+
Create Text object from dictionary.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
text_dict: Dictionary containing text data
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Text object
|
|
98
|
+
"""
|
|
99
|
+
position = text_dict.get("position", {"x": 0, "y": 0})
|
|
100
|
+
pos = point_from_dict_or_tuple(position)
|
|
101
|
+
|
|
102
|
+
return Text(
|
|
103
|
+
uuid=text_dict.get("uuid", str(uuid.uuid4())),
|
|
104
|
+
position=pos,
|
|
105
|
+
text=text_dict.get("text", ""),
|
|
106
|
+
rotation=text_dict.get("rotation", 0.0),
|
|
107
|
+
size=text_dict.get("size", 1.27),
|
|
108
|
+
exclude_from_sim=text_dict.get("exclude_from_sim", False),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def create_label(label_dict: Dict[str, Any]) -> Label:
|
|
113
|
+
"""
|
|
114
|
+
Create Label object from dictionary.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
label_dict: Dictionary containing label data
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Label object
|
|
121
|
+
"""
|
|
122
|
+
position = label_dict.get("position", {"x": 0, "y": 0})
|
|
123
|
+
pos = point_from_dict_or_tuple(position)
|
|
124
|
+
|
|
125
|
+
return Label(
|
|
126
|
+
uuid=label_dict.get("uuid", str(uuid.uuid4())),
|
|
127
|
+
position=pos,
|
|
128
|
+
text=label_dict.get("text", ""),
|
|
129
|
+
label_type=LabelType(label_dict.get("label_type", "local")),
|
|
130
|
+
rotation=label_dict.get("rotation", 0.0),
|
|
131
|
+
size=label_dict.get("size", 1.27),
|
|
132
|
+
shape=(
|
|
133
|
+
HierarchicalLabelShape(label_dict.get("shape"))
|
|
134
|
+
if label_dict.get("shape")
|
|
135
|
+
else None
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def create_no_connect(no_connect_dict: Dict[str, Any]) -> NoConnect:
|
|
141
|
+
"""
|
|
142
|
+
Create NoConnect object from dictionary.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
no_connect_dict: Dictionary containing no-connect data
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
NoConnect object
|
|
149
|
+
"""
|
|
150
|
+
position = no_connect_dict.get("position", {"x": 0, "y": 0})
|
|
151
|
+
pos = point_from_dict_or_tuple(position)
|
|
152
|
+
|
|
153
|
+
return NoConnect(
|
|
154
|
+
uuid=no_connect_dict.get("uuid", str(uuid.uuid4())),
|
|
155
|
+
position=pos,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def create_net(net_dict: Dict[str, Any]) -> Net:
|
|
160
|
+
"""
|
|
161
|
+
Create Net object from dictionary.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
net_dict: Dictionary containing net data
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Net object
|
|
168
|
+
"""
|
|
169
|
+
return Net(
|
|
170
|
+
name=net_dict.get("name", ""),
|
|
171
|
+
components=net_dict.get("components", []),
|
|
172
|
+
wires=net_dict.get("wires", []),
|
|
173
|
+
labels=net_dict.get("labels", []),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def create_wires_from_list(wire_data: List[Any]) -> List[Wire]:
|
|
178
|
+
"""
|
|
179
|
+
Create list of Wire objects from list of dictionaries.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
wire_data: List of wire dictionaries
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
List of Wire objects
|
|
186
|
+
"""
|
|
187
|
+
wires = []
|
|
188
|
+
for wire_dict in wire_data:
|
|
189
|
+
if isinstance(wire_dict, dict):
|
|
190
|
+
wires.append(ElementFactory.create_wire(wire_dict))
|
|
191
|
+
return wires
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def create_junctions_from_list(junction_data: List[Any]) -> List[Junction]:
|
|
195
|
+
"""
|
|
196
|
+
Create list of Junction objects from list of dictionaries.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
junction_data: List of junction dictionaries
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of Junction objects
|
|
203
|
+
"""
|
|
204
|
+
junctions = []
|
|
205
|
+
for junction_dict in junction_data:
|
|
206
|
+
if isinstance(junction_dict, dict):
|
|
207
|
+
junctions.append(ElementFactory.create_junction(junction_dict))
|
|
208
|
+
return junctions
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def create_texts_from_list(text_data: List[Any]) -> List[Text]:
|
|
212
|
+
"""
|
|
213
|
+
Create list of Text objects from list of dictionaries.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
text_data: List of text dictionaries
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
List of Text objects
|
|
220
|
+
"""
|
|
221
|
+
texts = []
|
|
222
|
+
for text_dict in text_data:
|
|
223
|
+
if isinstance(text_dict, dict):
|
|
224
|
+
texts.append(ElementFactory.create_text(text_dict))
|
|
225
|
+
return texts
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def create_labels_from_list(label_data: List[Any]) -> List[Label]:
|
|
229
|
+
"""
|
|
230
|
+
Create list of Label objects from list of dictionaries.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
label_data: List of label dictionaries
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of Label objects
|
|
237
|
+
"""
|
|
238
|
+
labels = []
|
|
239
|
+
for label_dict in label_data:
|
|
240
|
+
if isinstance(label_dict, dict):
|
|
241
|
+
labels.append(ElementFactory.create_label(label_dict))
|
|
242
|
+
return labels
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def create_no_connects_from_list(no_connect_data: List[Any]) -> List[NoConnect]:
|
|
246
|
+
"""
|
|
247
|
+
Create list of NoConnect objects from list of dictionaries.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
no_connect_data: List of no-connect dictionaries
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
List of NoConnect objects
|
|
254
|
+
"""
|
|
255
|
+
no_connects = []
|
|
256
|
+
for no_connect_dict in no_connect_data:
|
|
257
|
+
if isinstance(no_connect_dict, dict):
|
|
258
|
+
no_connects.append(ElementFactory.create_no_connect(no_connect_dict))
|
|
259
|
+
return no_connects
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def create_nets_from_list(net_data: List[Any]) -> List[Net]:
|
|
263
|
+
"""
|
|
264
|
+
Create list of Net objects from list of dictionaries.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
net_data: List of net dictionaries
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
List of Net objects
|
|
271
|
+
"""
|
|
272
|
+
nets = []
|
|
273
|
+
for net_dict in net_data:
|
|
274
|
+
if isinstance(net_dict, dict):
|
|
275
|
+
nets.append(ElementFactory.create_net(net_dict))
|
|
276
|
+
return nets
|
kicad_sch_api/core/junctions.py
CHANGED
|
@@ -9,55 +9,34 @@ import logging
|
|
|
9
9
|
import uuid as uuid_module
|
|
10
10
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
|
+
from .collections import BaseCollection
|
|
12
13
|
from .types import Junction, Point
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class JunctionCollection:
|
|
18
|
+
class JunctionCollection(BaseCollection[Junction]):
|
|
18
19
|
"""
|
|
19
20
|
Professional junction collection with enhanced management features.
|
|
20
21
|
|
|
22
|
+
Inherits from BaseCollection for standard operations and adds junction-specific
|
|
23
|
+
functionality.
|
|
24
|
+
|
|
21
25
|
Features:
|
|
22
|
-
- Fast UUID-based lookup and indexing
|
|
26
|
+
- Fast UUID-based lookup and indexing (inherited)
|
|
23
27
|
- Position-based junction queries
|
|
24
|
-
- Bulk operations for performance
|
|
28
|
+
- Bulk operations for performance (inherited)
|
|
25
29
|
- Validation and conflict detection
|
|
26
30
|
"""
|
|
27
31
|
|
|
28
|
-
def __init__(self, junctions: Optional[List[Junction]] = None):
|
|
32
|
+
def __init__(self, junctions: Optional[List[Junction]] = None) -> None:
|
|
29
33
|
"""
|
|
30
34
|
Initialize junction collection.
|
|
31
35
|
|
|
32
36
|
Args:
|
|
33
37
|
junctions: Initial list of junctions
|
|
34
38
|
"""
|
|
35
|
-
|
|
36
|
-
self._uuid_index: Dict[str, int] = {}
|
|
37
|
-
self._modified = False
|
|
38
|
-
|
|
39
|
-
# Build UUID index
|
|
40
|
-
self._rebuild_index()
|
|
41
|
-
|
|
42
|
-
logger.debug(f"JunctionCollection initialized with {len(self._junctions)} junctions")
|
|
43
|
-
|
|
44
|
-
def _rebuild_index(self):
|
|
45
|
-
"""Rebuild UUID index for fast lookups."""
|
|
46
|
-
self._uuid_index = {junction.uuid: i for i, junction in enumerate(self._junctions)}
|
|
47
|
-
|
|
48
|
-
def __len__(self) -> int:
|
|
49
|
-
"""Number of junctions in collection."""
|
|
50
|
-
return len(self._junctions)
|
|
51
|
-
|
|
52
|
-
def __iter__(self):
|
|
53
|
-
"""Iterate over junctions."""
|
|
54
|
-
return iter(self._junctions)
|
|
55
|
-
|
|
56
|
-
def __getitem__(self, uuid: str) -> Junction:
|
|
57
|
-
"""Get junction by UUID."""
|
|
58
|
-
if uuid not in self._uuid_index:
|
|
59
|
-
raise KeyError(f"Junction with UUID '{uuid}' not found")
|
|
60
|
-
return self._junctions[self._uuid_index[uuid]]
|
|
39
|
+
super().__init__(junctions, collection_name="junctions")
|
|
61
40
|
|
|
62
41
|
def add(
|
|
63
42
|
self,
|
|
@@ -94,35 +73,12 @@ class JunctionCollection:
|
|
|
94
73
|
# Create junction
|
|
95
74
|
junction = Junction(uuid=uuid, position=position, diameter=diameter, color=color)
|
|
96
75
|
|
|
97
|
-
# Add to collection
|
|
98
|
-
self.
|
|
99
|
-
self._uuid_index[uuid] = len(self._junctions) - 1
|
|
100
|
-
self._modified = True
|
|
76
|
+
# Add to collection using base class method
|
|
77
|
+
self._add_item(junction)
|
|
101
78
|
|
|
102
79
|
logger.debug(f"Added junction at {position}, UUID={uuid}")
|
|
103
80
|
return uuid
|
|
104
81
|
|
|
105
|
-
def remove(self, uuid: str) -> bool:
|
|
106
|
-
"""
|
|
107
|
-
Remove junction by UUID.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
uuid: Junction UUID to remove
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
True if junction was removed, False if not found
|
|
114
|
-
"""
|
|
115
|
-
if uuid not in self._uuid_index:
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
index = self._uuid_index[uuid]
|
|
119
|
-
del self._junctions[index]
|
|
120
|
-
self._rebuild_index()
|
|
121
|
-
self._modified = True
|
|
122
|
-
|
|
123
|
-
logger.debug(f"Removed junction: {uuid}")
|
|
124
|
-
return True
|
|
125
|
-
|
|
126
82
|
def get_at_position(
|
|
127
83
|
self, position: Union[Point, Tuple[float, float]], tolerance: float = 0.01
|
|
128
84
|
) -> Optional[Junction]:
|
|
@@ -139,7 +95,7 @@ class JunctionCollection:
|
|
|
139
95
|
if isinstance(position, tuple):
|
|
140
96
|
position = Point(position[0], position[1])
|
|
141
97
|
|
|
142
|
-
for junction in self.
|
|
98
|
+
for junction in self._items:
|
|
143
99
|
if junction.position.distance_to(position) <= tolerance:
|
|
144
100
|
return junction
|
|
145
101
|
|
|
@@ -162,40 +118,35 @@ class JunctionCollection:
|
|
|
162
118
|
point = Point(point[0], point[1])
|
|
163
119
|
|
|
164
120
|
matching_junctions = []
|
|
165
|
-
for junction in self.
|
|
121
|
+
for junction in self._items:
|
|
166
122
|
if junction.position.distance_to(point) <= tolerance:
|
|
167
123
|
matching_junctions.append(junction)
|
|
168
124
|
|
|
169
125
|
return matching_junctions
|
|
170
126
|
|
|
171
127
|
def get_statistics(self) -> Dict[str, Any]:
|
|
172
|
-
"""Get junction collection statistics."""
|
|
173
|
-
|
|
174
|
-
|
|
128
|
+
"""Get junction collection statistics (extends base statistics)."""
|
|
129
|
+
base_stats = super().get_statistics()
|
|
130
|
+
if not self._items:
|
|
131
|
+
return {**base_stats, "total_junctions": 0, "avg_diameter": 0, "positions": []}
|
|
175
132
|
|
|
176
|
-
avg_diameter = sum(j.diameter for j in self.
|
|
177
|
-
positions = [(j.position.x, j.position.y) for j in self.
|
|
133
|
+
avg_diameter = sum(j.diameter for j in self._items) / len(self._items)
|
|
134
|
+
positions = [(j.position.x, j.position.y) for j in self._items]
|
|
178
135
|
|
|
179
136
|
return {
|
|
180
|
-
|
|
137
|
+
**base_stats,
|
|
138
|
+
"total_junctions": len(self._items),
|
|
181
139
|
"avg_diameter": avg_diameter,
|
|
182
140
|
"positions": positions,
|
|
183
|
-
"unique_diameters": len(set(j.diameter for j in self.
|
|
184
|
-
"unique_colors": len(set(j.color for j in self.
|
|
141
|
+
"unique_diameters": len(set(j.diameter for j in self._items)),
|
|
142
|
+
"unique_colors": len(set(j.color for j in self._items)),
|
|
185
143
|
}
|
|
186
144
|
|
|
187
|
-
def clear(self):
|
|
188
|
-
"""Remove all junctions from collection."""
|
|
189
|
-
self._junctions.clear()
|
|
190
|
-
self._uuid_index.clear()
|
|
191
|
-
self._modified = True
|
|
192
|
-
logger.debug("Cleared all junctions")
|
|
193
|
-
|
|
194
145
|
@property
|
|
195
146
|
def modified(self) -> bool:
|
|
196
147
|
"""Check if collection has been modified."""
|
|
197
|
-
return self.
|
|
148
|
+
return self.is_modified()
|
|
198
149
|
|
|
199
|
-
def mark_saved(self):
|
|
150
|
+
def mark_saved(self) -> None:
|
|
200
151
|
"""Mark collection as saved (reset modified flag)."""
|
|
201
|
-
self.
|
|
152
|
+
self.reset_modified_flag()
|
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(
|