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.
- kicad_sch_api/collections/__init__.py +21 -0
- kicad_sch_api/collections/base.py +294 -0
- kicad_sch_api/collections/components.py +434 -0
- kicad_sch_api/collections/junctions.py +366 -0
- kicad_sch_api/collections/labels.py +404 -0
- kicad_sch_api/collections/wires.py +406 -0
- kicad_sch_api/core/components.py +5 -0
- kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api/core/labels.py +348 -0
- kicad_sch_api/core/managers/__init__.py +26 -0
- kicad_sch_api/core/managers/file_io.py +243 -0
- kicad_sch_api/core/managers/format_sync.py +501 -0
- kicad_sch_api/core/managers/graphics.py +579 -0
- kicad_sch_api/core/managers/metadata.py +268 -0
- kicad_sch_api/core/managers/sheet.py +454 -0
- kicad_sch_api/core/managers/text_elements.py +536 -0
- kicad_sch_api/core/managers/validation.py +474 -0
- kicad_sch_api/core/managers/wire.py +346 -0
- kicad_sch_api/core/nets.py +310 -0
- kicad_sch_api/core/no_connects.py +276 -0
- kicad_sch_api/core/parser.py +75 -41
- kicad_sch_api/core/schematic.py +904 -1074
- kicad_sch_api/core/texts.py +343 -0
- kicad_sch_api/core/types.py +13 -4
- kicad_sch_api/geometry/font_metrics.py +3 -1
- kicad_sch_api/geometry/symbol_bbox.py +56 -43
- kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api/parsers/base.py +145 -0
- kicad_sch_api/parsers/label_parser.py +254 -0
- kicad_sch_api/parsers/registry.py +155 -0
- kicad_sch_api/parsers/symbol_parser.py +222 -0
- kicad_sch_api/parsers/wire_parser.py +99 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api/symbols/validators.py +504 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/METADATA +1 -1
- kicad_sch_api-0.4.0.dist-info/RECORD +67 -0
- kicad_sch_api-0.3.4.dist-info/RECORD +0 -34
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.4.dist-info → kicad_sch_api-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
kicad_sch_api/core/components.py
CHANGED
|
@@ -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')."""
|
kicad_sch_api/core/formatter.py
CHANGED
|
@@ -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(
|
|
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)
|