kicad-sch-api 0.3.4__py3-none-any.whl → 0.4.0__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.

Files changed (47) hide show
  1. kicad_sch_api/collections/__init__.py +21 -0
  2. kicad_sch_api/collections/base.py +294 -0
  3. kicad_sch_api/collections/components.py +434 -0
  4. kicad_sch_api/collections/junctions.py +366 -0
  5. kicad_sch_api/collections/labels.py +404 -0
  6. kicad_sch_api/collections/wires.py +406 -0
  7. kicad_sch_api/core/components.py +5 -0
  8. kicad_sch_api/core/formatter.py +3 -1
  9. kicad_sch_api/core/labels.py +348 -0
  10. kicad_sch_api/core/managers/__init__.py +26 -0
  11. kicad_sch_api/core/managers/file_io.py +243 -0
  12. kicad_sch_api/core/managers/format_sync.py +501 -0
  13. kicad_sch_api/core/managers/graphics.py +579 -0
  14. kicad_sch_api/core/managers/metadata.py +268 -0
  15. kicad_sch_api/core/managers/sheet.py +454 -0
  16. kicad_sch_api/core/managers/text_elements.py +536 -0
  17. kicad_sch_api/core/managers/validation.py +474 -0
  18. kicad_sch_api/core/managers/wire.py +346 -0
  19. kicad_sch_api/core/nets.py +310 -0
  20. kicad_sch_api/core/no_connects.py +276 -0
  21. kicad_sch_api/core/parser.py +75 -41
  22. kicad_sch_api/core/schematic.py +904 -1074
  23. kicad_sch_api/core/texts.py +343 -0
  24. kicad_sch_api/core/types.py +13 -4
  25. kicad_sch_api/geometry/font_metrics.py +3 -1
  26. kicad_sch_api/geometry/symbol_bbox.py +56 -43
  27. kicad_sch_api/interfaces/__init__.py +17 -0
  28. kicad_sch_api/interfaces/parser.py +76 -0
  29. kicad_sch_api/interfaces/repository.py +70 -0
  30. kicad_sch_api/interfaces/resolver.py +117 -0
  31. kicad_sch_api/parsers/__init__.py +14 -0
  32. kicad_sch_api/parsers/base.py +145 -0
  33. kicad_sch_api/parsers/label_parser.py +254 -0
  34. kicad_sch_api/parsers/registry.py +155 -0
  35. kicad_sch_api/parsers/symbol_parser.py +222 -0
  36. kicad_sch_api/parsers/wire_parser.py +99 -0
  37. kicad_sch_api/symbols/__init__.py +18 -0
  38. kicad_sch_api/symbols/cache.py +467 -0
  39. kicad_sch_api/symbols/resolver.py +361 -0
  40. kicad_sch_api/symbols/validators.py +504 -0
  41. {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/METADATA +1 -1
  42. kicad_sch_api-0.4.0.dist-info/RECORD +67 -0
  43. kicad_sch_api-0.3.4.dist-info/RECORD +0 -34
  44. {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/WHEEL +0 -0
  45. {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/entry_points.txt +0 -0
  46. {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/licenses/LICENSE +0 -0
  47. {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,406 @@
1
+ """
2
+ Wire collection with specialized indexing and wire-specific operations.
3
+
4
+ Extends the base IndexedCollection to provide wire-specific features like
5
+ endpoint indexing, multi-point wire support, and connectivity management.
6
+ """
7
+
8
+ import logging
9
+ import uuid as uuid_module
10
+ from typing import Any, Dict, List, Optional, Tuple, Union
11
+
12
+ from ..core.types import Point, Wire, WireType
13
+ from .base import IndexedCollection
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class WireCollection(IndexedCollection[Wire]):
19
+ """
20
+ Professional wire collection with enhanced management features.
21
+
22
+ Extends IndexedCollection with wire-specific features:
23
+ - Endpoint indexing for connectivity analysis
24
+ - Multi-point wire support
25
+ - Wire type classification (normal, bus, etc.)
26
+ - Bulk operations for performance
27
+ - Junction management integration
28
+ """
29
+
30
+ def __init__(self, wires: Optional[List[Wire]] = None):
31
+ """
32
+ Initialize wire collection.
33
+
34
+ Args:
35
+ wires: Initial list of wires
36
+ """
37
+ self._endpoint_index: Dict[Tuple[float, float], List[Wire]] = {}
38
+ self._type_index: Dict[WireType, List[Wire]] = {}
39
+
40
+ super().__init__(wires)
41
+
42
+ # Abstract method implementations
43
+ def _get_item_uuid(self, item: Wire) -> str:
44
+ """Extract UUID from wire."""
45
+ return item.uuid
46
+
47
+ def _create_item(self, **kwargs) -> Wire:
48
+ """Create a new wire with given parameters."""
49
+ # This will be called by add() methods
50
+ raise NotImplementedError("Use add() method instead")
51
+
52
+ def _build_additional_indexes(self) -> None:
53
+ """Build wire-specific indexes."""
54
+ # Clear existing indexes
55
+ self._endpoint_index.clear()
56
+ self._type_index.clear()
57
+
58
+ # Rebuild indexes from current items
59
+ for wire in self._items:
60
+ # Endpoint index - index by all endpoints
61
+ for point in wire.points:
62
+ endpoint = (point.x, point.y)
63
+ if endpoint not in self._endpoint_index:
64
+ self._endpoint_index[endpoint] = []
65
+ self._endpoint_index[endpoint].append(wire)
66
+
67
+ # Type index
68
+ wire_type = getattr(wire, "wire_type", WireType.WIRE)
69
+ if wire_type not in self._type_index:
70
+ self._type_index[wire_type] = []
71
+ self._type_index[wire_type].append(wire)
72
+
73
+ # Wire-specific methods
74
+ def add(
75
+ self,
76
+ start: Union[Point, Tuple[float, float]],
77
+ end: Union[Point, Tuple[float, float]],
78
+ wire_type: WireType = WireType.WIRE,
79
+ stroke_width: float = 0.0,
80
+ stroke_type: str = "default",
81
+ wire_uuid: Optional[str] = None,
82
+ ) -> Wire:
83
+ """
84
+ Add a new wire to the collection.
85
+
86
+ Args:
87
+ start: Starting point of the wire
88
+ end: Ending point of the wire
89
+ wire_type: Type of wire (normal, bus, etc.)
90
+ stroke_width: Wire stroke width
91
+ stroke_type: Wire stroke type
92
+ wire_uuid: Specific UUID for wire (auto-generated if None)
93
+
94
+ Returns:
95
+ Newly created Wire
96
+ """
97
+ # Convert tuples to Points if needed
98
+ if isinstance(start, tuple):
99
+ start = Point(start[0], start[1])
100
+ if isinstance(end, tuple):
101
+ end = Point(end[0], end[1])
102
+
103
+ # Validate wire points
104
+ if start == end:
105
+ raise ValueError("Wire start and end points cannot be the same")
106
+
107
+ # Generate UUID if not provided
108
+ if wire_uuid is None:
109
+ wire_uuid = str(uuid_module.uuid4())
110
+
111
+ # Create wire
112
+ wire = Wire(
113
+ uuid=wire_uuid,
114
+ points=[start, end],
115
+ wire_type=wire_type,
116
+ stroke_width=stroke_width,
117
+ stroke_type=stroke_type,
118
+ )
119
+
120
+ # Add to collection using base class method
121
+ return super().add(wire)
122
+
123
+ def add_multi_point(
124
+ self,
125
+ points: List[Union[Point, Tuple[float, float]]],
126
+ wire_type: WireType = WireType.WIRE,
127
+ stroke_width: float = 0.0,
128
+ stroke_type: str = "default",
129
+ wire_uuid: Optional[str] = None,
130
+ ) -> Wire:
131
+ """
132
+ Add a multi-point wire to the collection.
133
+
134
+ Args:
135
+ points: List of points defining the wire path
136
+ wire_type: Type of wire (normal, bus, etc.)
137
+ stroke_width: Wire stroke width
138
+ stroke_type: Wire stroke type
139
+ wire_uuid: Specific UUID for wire (auto-generated if None)
140
+
141
+ Returns:
142
+ Newly created Wire
143
+
144
+ Raises:
145
+ ValueError: If less than 2 points provided
146
+ """
147
+ if len(points) < 2:
148
+ raise ValueError("Wire must have at least 2 points")
149
+
150
+ # Convert tuples to Points if needed
151
+ converted_points = []
152
+ for point in points:
153
+ if isinstance(point, tuple):
154
+ converted_points.append(Point(point[0], point[1]))
155
+ else:
156
+ converted_points.append(point)
157
+
158
+ # Generate UUID if not provided
159
+ if wire_uuid is None:
160
+ wire_uuid = str(uuid_module.uuid4())
161
+
162
+ # Create wire
163
+ wire = Wire(
164
+ uuid=wire_uuid,
165
+ points=converted_points,
166
+ wire_type=wire_type,
167
+ stroke_width=stroke_width,
168
+ stroke_type=stroke_type,
169
+ )
170
+
171
+ # Add to collection using base class method
172
+ return super().add(wire)
173
+
174
+ def get_wires_at_point(self, point: Union[Point, Tuple[float, float]]) -> List[Wire]:
175
+ """
176
+ Get all wires that pass through a specific point.
177
+
178
+ Args:
179
+ point: Point to search at
180
+
181
+ Returns:
182
+ List of wires passing through the point
183
+ """
184
+ self._ensure_indexes_current()
185
+
186
+ if isinstance(point, Point):
187
+ endpoint = (point.x, point.y)
188
+ else:
189
+ endpoint = point
190
+
191
+ return self._endpoint_index.get(endpoint, []).copy()
192
+
193
+ def get_wires_by_type(self, wire_type: WireType) -> List[Wire]:
194
+ """
195
+ Get all wires of a specific type.
196
+
197
+ Args:
198
+ wire_type: Type of wires to find
199
+
200
+ Returns:
201
+ List of wires of the specified type
202
+ """
203
+ self._ensure_indexes_current()
204
+ return self._type_index.get(wire_type, []).copy()
205
+
206
+ def get_connected_wires(self, wire: Wire) -> List[Wire]:
207
+ """
208
+ Get all wires connected to the given wire.
209
+
210
+ Args:
211
+ wire: Wire to find connections for
212
+
213
+ Returns:
214
+ List of connected wires (excluding the input wire)
215
+ """
216
+ connected = set()
217
+
218
+ # Check connections at all endpoints
219
+ for point in wire.points:
220
+ endpoint_wires = self.get_wires_at_point(point)
221
+ for endpoint_wire in endpoint_wires:
222
+ if endpoint_wire.uuid != wire.uuid:
223
+ connected.add(endpoint_wire)
224
+
225
+ return list(connected)
226
+
227
+ def find_wire_networks(self) -> List[List[Wire]]:
228
+ """
229
+ Find all connected wire networks in the collection.
230
+
231
+ Returns:
232
+ List of wire networks, each network is a list of connected wires
233
+ """
234
+ visited = set()
235
+ networks = []
236
+
237
+ for wire in self._items:
238
+ if wire.uuid in visited:
239
+ continue
240
+
241
+ # Start a new network with this wire
242
+ network = []
243
+ to_visit = [wire]
244
+
245
+ while to_visit:
246
+ current_wire = to_visit.pop()
247
+ if current_wire.uuid in visited:
248
+ continue
249
+
250
+ visited.add(current_wire.uuid)
251
+ network.append(current_wire)
252
+
253
+ # Add all connected wires to visit list
254
+ connected = self.get_connected_wires(current_wire)
255
+ for connected_wire in connected:
256
+ if connected_wire.uuid not in visited:
257
+ to_visit.append(connected_wire)
258
+
259
+ if network:
260
+ networks.append(network)
261
+
262
+ return networks
263
+
264
+ def modify_wire_path(
265
+ self, wire_uuid: str, new_points: List[Union[Point, Tuple[float, float]]]
266
+ ) -> bool:
267
+ """
268
+ Modify the path of an existing wire.
269
+
270
+ Args:
271
+ wire_uuid: UUID of wire to modify
272
+ new_points: New points for the wire path
273
+
274
+ Returns:
275
+ True if wire was modified, False if not found
276
+
277
+ Raises:
278
+ ValueError: If less than 2 points provided
279
+ """
280
+ if len(new_points) < 2:
281
+ raise ValueError("Wire must have at least 2 points")
282
+
283
+ wire = self.get(wire_uuid)
284
+ if not wire:
285
+ return False
286
+
287
+ # Convert tuples to Points if needed
288
+ converted_points = []
289
+ for point in new_points:
290
+ if isinstance(point, tuple):
291
+ converted_points.append(Point(point[0], point[1]))
292
+ else:
293
+ converted_points.append(point)
294
+
295
+ # Update wire points
296
+ wire.points = converted_points
297
+ self._mark_modified()
298
+ self._mark_indexes_dirty()
299
+
300
+ logger.debug(f"Modified wire {wire_uuid} path with {len(new_points)} points")
301
+ return True
302
+
303
+ # Bulk operations for performance
304
+ def remove_wires_at_point(self, point: Union[Point, Tuple[float, float]]) -> int:
305
+ """
306
+ Remove all wires passing through a specific point.
307
+
308
+ Args:
309
+ point: Point where wires should be removed
310
+
311
+ Returns:
312
+ Number of wires removed
313
+ """
314
+ wires_to_remove = self.get_wires_at_point(point)
315
+
316
+ for wire in wires_to_remove:
317
+ self.remove(wire.uuid)
318
+
319
+ logger.info(f"Removed {len(wires_to_remove)} wires at point {point}")
320
+ return len(wires_to_remove)
321
+
322
+ def bulk_update_stroke(
323
+ self,
324
+ wire_type: Optional[WireType] = None,
325
+ stroke_width: Optional[float] = None,
326
+ stroke_type: Optional[str] = None,
327
+ ) -> int:
328
+ """
329
+ Bulk update stroke properties for wires.
330
+
331
+ Args:
332
+ wire_type: Filter by wire type (None for all)
333
+ stroke_width: New stroke width (None to keep existing)
334
+ stroke_type: New stroke type (None to keep existing)
335
+
336
+ Returns:
337
+ Number of wires updated
338
+ """
339
+ # Get wires to update
340
+ if wire_type is not None:
341
+ wires_to_update = self.get_wires_by_type(wire_type)
342
+ else:
343
+ wires_to_update = list(self._items)
344
+
345
+ # Apply updates
346
+ for wire in wires_to_update:
347
+ if stroke_width is not None:
348
+ wire.stroke_width = stroke_width
349
+ if stroke_type is not None:
350
+ wire.stroke_type = stroke_type
351
+
352
+ if wires_to_update:
353
+ self._mark_modified()
354
+
355
+ logger.info(f"Bulk updated stroke properties for {len(wires_to_update)} wires")
356
+ return len(wires_to_update)
357
+
358
+ # Collection statistics
359
+ def get_connection_statistics(self) -> Dict[str, Any]:
360
+ """
361
+ Get connection statistics for the wire collection.
362
+
363
+ Returns:
364
+ Dictionary with connection statistics
365
+ """
366
+ stats = super().get_statistics()
367
+
368
+ # Add wire-specific statistics
369
+ stats.update(
370
+ {
371
+ "endpoint_count": len(self._endpoint_index),
372
+ "wire_types": {
373
+ wire_type.value: len(wires) for wire_type, wires in self._type_index.items()
374
+ },
375
+ "networks": len(self.find_wire_networks()),
376
+ "total_length": sum(self._calculate_wire_length(wire) for wire in self._items),
377
+ }
378
+ )
379
+
380
+ return stats
381
+
382
+ def _calculate_wire_length(self, wire: Wire) -> float:
383
+ """
384
+ Calculate the total length of a wire.
385
+
386
+ Args:
387
+ wire: Wire to calculate length for
388
+
389
+ Returns:
390
+ Total wire length
391
+ """
392
+ if len(wire.points) < 2:
393
+ return 0.0
394
+
395
+ total_length = 0.0
396
+ for i in range(len(wire.points) - 1):
397
+ start_point = wire.points[i]
398
+ end_point = wire.points[i + 1]
399
+
400
+ dx = end_point.x - start_point.x
401
+ dy = end_point.y - start_point.y
402
+ segment_length = (dx**2 + dy**2) ** 0.5
403
+
404
+ total_length += segment_length
405
+
406
+ return total_length
@@ -38,6 +38,11 @@ class Component:
38
38
  self._validator = SchematicValidator()
39
39
 
40
40
  # Core properties with validation
41
+ @property
42
+ def uuid(self) -> str:
43
+ """Component UUID."""
44
+ return self._data.uuid
45
+
41
46
  @property
42
47
  def reference(self) -> str:
43
48
  """Component reference (e.g., 'R1')."""
@@ -55,7 +55,9 @@ class ExactFormatter:
55
55
  self.rules["generator"] = FormatRule(inline=True, quote_indices={1})
56
56
  self.rules["generator_version"] = FormatRule(inline=True, quote_indices={1})
57
57
  self.rules["uuid"] = FormatRule(inline=True, quote_indices={1})
58
- self.rules["paper"] = FormatRule(inline=True, quote_indices={1}) # Paper size should be quoted per KiCad format
58
+ self.rules["paper"] = FormatRule(
59
+ inline=True, quote_indices={1}
60
+ ) # Paper size should be quoted per KiCad format
59
61
 
60
62
  # Title block
61
63
  self.rules["title_block"] = FormatRule(inline=False)