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.
Files changed (56) 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/core/collections/__init__.py +5 -0
  10. kicad_sch_api/core/collections/base.py +248 -0
  11. kicad_sch_api/core/component_bounds.py +5 -0
  12. kicad_sch_api/core/components.py +62 -45
  13. kicad_sch_api/core/config.py +85 -3
  14. kicad_sch_api/core/factories/__init__.py +5 -0
  15. kicad_sch_api/core/factories/element_factory.py +276 -0
  16. kicad_sch_api/core/junctions.py +26 -75
  17. kicad_sch_api/core/labels.py +28 -52
  18. kicad_sch_api/core/managers/file_io.py +3 -2
  19. kicad_sch_api/core/managers/metadata.py +6 -5
  20. kicad_sch_api/core/managers/validation.py +3 -2
  21. kicad_sch_api/core/managers/wire.py +7 -1
  22. kicad_sch_api/core/nets.py +38 -43
  23. kicad_sch_api/core/no_connects.py +29 -53
  24. kicad_sch_api/core/parser.py +75 -1765
  25. kicad_sch_api/core/schematic.py +211 -148
  26. kicad_sch_api/core/texts.py +28 -55
  27. kicad_sch_api/core/types.py +59 -18
  28. kicad_sch_api/core/wires.py +27 -75
  29. kicad_sch_api/parsers/elements/__init__.py +22 -0
  30. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  31. kicad_sch_api/parsers/elements/label_parser.py +194 -0
  32. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  33. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  34. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  35. kicad_sch_api/parsers/elements/symbol_parser.py +313 -0
  36. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  37. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  38. kicad_sch_api/parsers/utils.py +80 -0
  39. kicad_sch_api/validation/__init__.py +25 -0
  40. kicad_sch_api/validation/erc.py +171 -0
  41. kicad_sch_api/validation/erc_models.py +203 -0
  42. kicad_sch_api/validation/pin_matrix.py +243 -0
  43. kicad_sch_api/validation/validators.py +391 -0
  44. {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
  45. kicad_sch_api-0.4.1.dist-info/RECORD +87 -0
  46. kicad_sch_api/core/manhattan_routing.py +0 -430
  47. kicad_sch_api/core/simple_manhattan.py +0 -228
  48. kicad_sch_api/core/wire_routing.py +0 -380
  49. kicad_sch_api/parsers/label_parser.py +0 -254
  50. kicad_sch_api/parsers/symbol_parser.py +0 -222
  51. kicad_sch_api/parsers/wire_parser.py +0 -99
  52. kicad_sch_api-0.4.0.dist-info/RECORD +0 -67
  53. {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
  54. {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
  55. {kicad_sch_api-0.4.0.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
  56. {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
@@ -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
- self._junctions: List[Junction] = junctions or []
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._junctions.append(junction)
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._junctions:
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._junctions:
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
- if not self._junctions:
174
- return {"total_junctions": 0, "avg_diameter": 0, "positions": []}
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._junctions) / len(self._junctions)
177
- positions = [(j.position.x, j.position.y) for j in self._junctions]
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
- "total_junctions": len(self._junctions),
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._junctions)),
184
- "unique_colors": len(set(j.color for j in self._junctions)),
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._modified
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._modified = False
152
+ self.reset_modified_flag()
@@ -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
- self._labels: List[LabelElement] = []
132
- self._uuid_index: Dict[str, LabelElement] = {}
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
- def get(self, label_uuid: str) -> Optional[LabelElement]:
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._uuid_index.get(label_uuid)
223
+ label_element = self.get(label_uuid)
223
224
  if not label_element:
224
225
  return False
225
226
 
226
- # Remove from indexes
227
- self._remove_from_indexes(label_element)
228
- self._mark_modified()
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._labels:
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._labels if predicate(label)]
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._labels:
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
- self._mark_modified()
295
+ super().clear()
291
296
 
292
297
  def _add_to_indexes(self, label_element: LabelElement):
293
- """Add label to internal indexes."""
294
- self._labels.append(label_element)
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
- def _mark_modified(self):
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._labels) > 0
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": "kicad-sch-api",
223
+ "generator": config.file_format.generator_default,
223
224
  "uuid": None, # Will be set by calling code
224
- "paper": "A4",
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
- valid_sizes = ["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]
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
- valid_sizes = ["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]
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
- valid_papers = ["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]
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
- # This would involve following wire networks through junctions
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(