kicad-sch-api 0.3.2__py3-none-any.whl → 0.3.5__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/collections/__init__.py +21 -0
- kicad_sch_api/collections/base.py +296 -0
- kicad_sch_api/collections/components.py +422 -0
- kicad_sch_api/collections/junctions.py +378 -0
- kicad_sch_api/collections/labels.py +412 -0
- kicad_sch_api/collections/wires.py +407 -0
- kicad_sch_api/core/formatter.py +31 -0
- kicad_sch_api/core/labels.py +348 -0
- kicad_sch_api/core/nets.py +310 -0
- kicad_sch_api/core/no_connects.py +274 -0
- kicad_sch_api/core/parser.py +72 -0
- kicad_sch_api/core/schematic.py +185 -9
- kicad_sch_api/core/texts.py +343 -0
- kicad_sch_api/core/types.py +26 -0
- kicad_sch_api/geometry/__init__.py +26 -0
- kicad_sch_api/geometry/font_metrics.py +20 -0
- kicad_sch_api/geometry/symbol_bbox.py +589 -0
- 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 +148 -0
- kicad_sch_api/parsers/label_parser.py +254 -0
- kicad_sch_api/parsers/registry.py +153 -0
- kicad_sch_api/parsers/symbol_parser.py +227 -0
- kicad_sch_api/parsers/wire_parser.py +99 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +470 -0
- kicad_sch_api/symbols/resolver.py +367 -0
- kicad_sch_api/symbols/validators.py +453 -0
- {kicad_sch_api-0.3.2.dist-info → kicad_sch_api-0.3.5.dist-info}/METADATA +1 -1
- kicad_sch_api-0.3.5.dist-info/RECORD +58 -0
- kicad_sch_api-0.3.2.dist-info/RECORD +0 -31
- {kicad_sch_api-0.3.2.dist-info → kicad_sch_api-0.3.5.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.2.dist-info → kicad_sch_api-0.3.5.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.2.dist-info → kicad_sch_api-0.3.5.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.3.2.dist-info → kicad_sch_api-0.3.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Component collection with specialized indexing and component-specific operations.
|
|
3
|
+
|
|
4
|
+
Extends the base IndexedCollection to provide component-specific features like
|
|
5
|
+
reference indexing, lib_id grouping, and automatic reference generation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
11
|
+
|
|
12
|
+
from ..core.types import Point, SchematicSymbol
|
|
13
|
+
from ..library.cache import get_symbol_cache
|
|
14
|
+
from ..utils.validation import SchematicValidator, ValidationError
|
|
15
|
+
from .base import IndexedCollection
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Component:
|
|
21
|
+
"""
|
|
22
|
+
Enhanced wrapper for schematic components with modern API.
|
|
23
|
+
|
|
24
|
+
Provides intuitive access to component properties, pins, and operations
|
|
25
|
+
while maintaining exact format preservation for professional use.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, symbol_data: SchematicSymbol, parent_collection: "ComponentCollection"):
|
|
29
|
+
"""
|
|
30
|
+
Initialize component wrapper.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
symbol_data: Underlying symbol data
|
|
34
|
+
parent_collection: Parent collection for updates
|
|
35
|
+
"""
|
|
36
|
+
self._data = symbol_data
|
|
37
|
+
self._collection = parent_collection
|
|
38
|
+
self._validator = SchematicValidator()
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def uuid(self) -> str:
|
|
42
|
+
"""Component UUID."""
|
|
43
|
+
return self._data.uuid
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def reference(self) -> str:
|
|
47
|
+
"""Component reference (e.g., 'R1')."""
|
|
48
|
+
return self._data.reference
|
|
49
|
+
|
|
50
|
+
@reference.setter
|
|
51
|
+
def reference(self, value: str):
|
|
52
|
+
"""Set component reference with validation."""
|
|
53
|
+
if not self._validator.validate_reference(value):
|
|
54
|
+
raise ValidationError(f"Invalid reference format: {value}")
|
|
55
|
+
|
|
56
|
+
# Check for duplicates in parent collection
|
|
57
|
+
if self._collection.get_by_reference(value) is not None:
|
|
58
|
+
raise ValidationError(f"Reference {value} already exists")
|
|
59
|
+
|
|
60
|
+
old_ref = self._data.reference
|
|
61
|
+
self._data.reference = value
|
|
62
|
+
self._collection._update_reference_index(old_ref, value)
|
|
63
|
+
self._collection._mark_modified()
|
|
64
|
+
logger.debug(f"Updated reference: {old_ref} -> {value}")
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def value(self) -> str:
|
|
68
|
+
"""Component value (e.g., '10k')."""
|
|
69
|
+
return self._data.value
|
|
70
|
+
|
|
71
|
+
@value.setter
|
|
72
|
+
def value(self, value: str):
|
|
73
|
+
"""Set component value."""
|
|
74
|
+
self._data.value = value
|
|
75
|
+
self._collection._mark_modified()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def lib_id(self) -> str:
|
|
79
|
+
"""Component library ID (e.g., 'Device:R')."""
|
|
80
|
+
return self._data.lib_id
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def position(self) -> Point:
|
|
84
|
+
"""Component position."""
|
|
85
|
+
return self._data.position
|
|
86
|
+
|
|
87
|
+
@position.setter
|
|
88
|
+
def position(self, value: Union[Point, Tuple[float, float]]):
|
|
89
|
+
"""Set component position."""
|
|
90
|
+
if isinstance(value, tuple):
|
|
91
|
+
value = Point(value[0], value[1])
|
|
92
|
+
self._data.position = value
|
|
93
|
+
self._collection._mark_modified()
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def rotation(self) -> float:
|
|
97
|
+
"""Component rotation in degrees."""
|
|
98
|
+
return self._data.rotation
|
|
99
|
+
|
|
100
|
+
@rotation.setter
|
|
101
|
+
def rotation(self, value: float):
|
|
102
|
+
"""Set component rotation."""
|
|
103
|
+
self._data.rotation = value
|
|
104
|
+
self._collection._mark_modified()
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def footprint(self) -> Optional[str]:
|
|
108
|
+
"""Component footprint."""
|
|
109
|
+
return self._data.footprint
|
|
110
|
+
|
|
111
|
+
@footprint.setter
|
|
112
|
+
def footprint(self, value: Optional[str]):
|
|
113
|
+
"""Set component footprint."""
|
|
114
|
+
self._data.footprint = value
|
|
115
|
+
self._collection._mark_modified()
|
|
116
|
+
|
|
117
|
+
def get_property(self, name: str) -> Optional[str]:
|
|
118
|
+
"""Get component property value."""
|
|
119
|
+
return self._data.properties.get(name)
|
|
120
|
+
|
|
121
|
+
def set_property(self, name: str, value: str) -> None:
|
|
122
|
+
"""Set component property value."""
|
|
123
|
+
self._data.properties[name] = value
|
|
124
|
+
self._collection._mark_modified()
|
|
125
|
+
|
|
126
|
+
def __repr__(self) -> str:
|
|
127
|
+
"""Detailed representation."""
|
|
128
|
+
return (
|
|
129
|
+
f"Component(ref='{self.reference}', lib_id='{self.lib_id}', "
|
|
130
|
+
f"value='{self.value}', pos={self.position}, rotation={self.rotation})"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ComponentCollection(IndexedCollection[Component]):
|
|
135
|
+
"""
|
|
136
|
+
Collection class for efficient component management.
|
|
137
|
+
|
|
138
|
+
Extends IndexedCollection with component-specific features:
|
|
139
|
+
- Reference-based indexing for fast component lookup
|
|
140
|
+
- Lib_id grouping for filtering by component type
|
|
141
|
+
- Value indexing for filtering by component value
|
|
142
|
+
- Automatic reference generation
|
|
143
|
+
- Component validation and conflict detection
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def __init__(self, components: Optional[List[SchematicSymbol]] = None):
|
|
147
|
+
"""
|
|
148
|
+
Initialize component collection.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
components: Initial list of component data
|
|
152
|
+
"""
|
|
153
|
+
self._reference_index: Dict[str, Component] = {}
|
|
154
|
+
self._lib_id_index: Dict[str, List[Component]] = {}
|
|
155
|
+
self._value_index: Dict[str, List[Component]] = {}
|
|
156
|
+
|
|
157
|
+
# Initialize base collection
|
|
158
|
+
wrapped_components = []
|
|
159
|
+
if components:
|
|
160
|
+
wrapped_components = [Component(comp_data, self) for comp_data in components]
|
|
161
|
+
|
|
162
|
+
super().__init__(wrapped_components)
|
|
163
|
+
|
|
164
|
+
# Abstract method implementations
|
|
165
|
+
def _get_item_uuid(self, item: Component) -> str:
|
|
166
|
+
"""Extract UUID from component."""
|
|
167
|
+
return item.uuid
|
|
168
|
+
|
|
169
|
+
def _create_item(self, **kwargs) -> Component:
|
|
170
|
+
"""Create a new component with given parameters."""
|
|
171
|
+
# This will be called by add() methods in subclasses
|
|
172
|
+
raise NotImplementedError("Use add() method instead")
|
|
173
|
+
|
|
174
|
+
def _build_additional_indexes(self) -> None:
|
|
175
|
+
"""Build component-specific indexes."""
|
|
176
|
+
# Clear existing indexes
|
|
177
|
+
self._reference_index.clear()
|
|
178
|
+
self._lib_id_index.clear()
|
|
179
|
+
self._value_index.clear()
|
|
180
|
+
|
|
181
|
+
# Rebuild indexes from current items
|
|
182
|
+
for component in self._items:
|
|
183
|
+
# Reference index
|
|
184
|
+
self._reference_index[component.reference] = component
|
|
185
|
+
|
|
186
|
+
# Lib_id index
|
|
187
|
+
if component.lib_id not in self._lib_id_index:
|
|
188
|
+
self._lib_id_index[component.lib_id] = []
|
|
189
|
+
self._lib_id_index[component.lib_id].append(component)
|
|
190
|
+
|
|
191
|
+
# Value index
|
|
192
|
+
if component.value not in self._value_index:
|
|
193
|
+
self._value_index[component.value] = []
|
|
194
|
+
self._value_index[component.value].append(component)
|
|
195
|
+
|
|
196
|
+
# Component-specific methods
|
|
197
|
+
def add(
|
|
198
|
+
self,
|
|
199
|
+
lib_id: str,
|
|
200
|
+
reference: Optional[str] = None,
|
|
201
|
+
value: str = "",
|
|
202
|
+
position: Optional[Union[Point, Tuple[float, float]]] = None,
|
|
203
|
+
footprint: Optional[str] = None,
|
|
204
|
+
unit: int = 1,
|
|
205
|
+
component_uuid: Optional[str] = None,
|
|
206
|
+
**properties,
|
|
207
|
+
) -> Component:
|
|
208
|
+
"""
|
|
209
|
+
Add a new component to the schematic.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
lib_id: Library identifier (e.g., "Device:R")
|
|
213
|
+
reference: Component reference (auto-generated if None)
|
|
214
|
+
value: Component value
|
|
215
|
+
position: Component position (auto-placed if None)
|
|
216
|
+
footprint: Component footprint
|
|
217
|
+
unit: Unit number for multi-unit components (1-based)
|
|
218
|
+
component_uuid: Specific UUID for component (auto-generated if None)
|
|
219
|
+
**properties: Additional component properties
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Newly created Component
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
ValidationError: If component data is invalid
|
|
226
|
+
"""
|
|
227
|
+
# Validate lib_id
|
|
228
|
+
validator = SchematicValidator()
|
|
229
|
+
if not validator.validate_lib_id(lib_id):
|
|
230
|
+
raise ValidationError(f"Invalid lib_id format: {lib_id}")
|
|
231
|
+
|
|
232
|
+
# Generate reference if not provided
|
|
233
|
+
if not reference:
|
|
234
|
+
reference = self._generate_reference(lib_id)
|
|
235
|
+
|
|
236
|
+
# Validate reference
|
|
237
|
+
if not validator.validate_reference(reference):
|
|
238
|
+
raise ValidationError(f"Invalid reference format: {reference}")
|
|
239
|
+
|
|
240
|
+
# Check for duplicate reference
|
|
241
|
+
self._ensure_indexes_current()
|
|
242
|
+
if reference in self._reference_index:
|
|
243
|
+
raise ValidationError(f"Reference {reference} already exists")
|
|
244
|
+
|
|
245
|
+
# Set default position if not provided
|
|
246
|
+
if position is None:
|
|
247
|
+
position = self._find_available_position()
|
|
248
|
+
elif isinstance(position, tuple):
|
|
249
|
+
position = Point(position[0], position[1])
|
|
250
|
+
|
|
251
|
+
# Always snap component position to KiCAD grid (1.27mm = 50mil)
|
|
252
|
+
from ..core.geometry import snap_to_grid
|
|
253
|
+
snapped_pos = snap_to_grid((position.x, position.y), grid_size=1.27)
|
|
254
|
+
position = Point(snapped_pos[0], snapped_pos[1])
|
|
255
|
+
|
|
256
|
+
# Generate UUID if not provided
|
|
257
|
+
if component_uuid is None:
|
|
258
|
+
component_uuid = str(uuid.uuid4())
|
|
259
|
+
|
|
260
|
+
# Create SchematicSymbol data
|
|
261
|
+
symbol_data = SchematicSymbol(
|
|
262
|
+
uuid=component_uuid,
|
|
263
|
+
lib_id=lib_id,
|
|
264
|
+
reference=reference,
|
|
265
|
+
value=value,
|
|
266
|
+
position=position,
|
|
267
|
+
rotation=0.0,
|
|
268
|
+
unit=unit,
|
|
269
|
+
in_bom=True,
|
|
270
|
+
on_board=True,
|
|
271
|
+
footprint=footprint,
|
|
272
|
+
properties=properties.copy()
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Create component wrapper
|
|
276
|
+
component = Component(symbol_data, self)
|
|
277
|
+
|
|
278
|
+
# Add to collection using base class method
|
|
279
|
+
return super().add(component)
|
|
280
|
+
|
|
281
|
+
def get_by_reference(self, reference: str) -> Optional[Component]:
|
|
282
|
+
"""
|
|
283
|
+
Get component by reference.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
reference: Component reference to find
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Component if found, None otherwise
|
|
290
|
+
"""
|
|
291
|
+
self._ensure_indexes_current()
|
|
292
|
+
return self._reference_index.get(reference)
|
|
293
|
+
|
|
294
|
+
def get_by_lib_id(self, lib_id: str) -> List[Component]:
|
|
295
|
+
"""
|
|
296
|
+
Get all components with a specific lib_id.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
lib_id: Library ID to search for
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
List of matching components
|
|
303
|
+
"""
|
|
304
|
+
self._ensure_indexes_current()
|
|
305
|
+
return self._lib_id_index.get(lib_id, []).copy()
|
|
306
|
+
|
|
307
|
+
def get_by_value(self, value: str) -> List[Component]:
|
|
308
|
+
"""
|
|
309
|
+
Get all components with a specific value.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
value: Component value to search for
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
List of matching components
|
|
316
|
+
"""
|
|
317
|
+
self._ensure_indexes_current()
|
|
318
|
+
return self._value_index.get(value, []).copy()
|
|
319
|
+
|
|
320
|
+
def _generate_reference(self, lib_id: str) -> str:
|
|
321
|
+
"""
|
|
322
|
+
Generate a unique reference for a component.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
lib_id: Library ID to generate reference for
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Unique reference string
|
|
329
|
+
"""
|
|
330
|
+
# Extract base reference from lib_id
|
|
331
|
+
if ":" in lib_id:
|
|
332
|
+
base_ref = lib_id.split(":")[-1]
|
|
333
|
+
else:
|
|
334
|
+
base_ref = lib_id
|
|
335
|
+
|
|
336
|
+
# Map common component types to standard prefixes
|
|
337
|
+
ref_prefixes = {
|
|
338
|
+
"R": "R", "Resistor": "R",
|
|
339
|
+
"C": "C", "Capacitor": "C",
|
|
340
|
+
"L": "L", "Inductor": "L",
|
|
341
|
+
"D": "D", "Diode": "D",
|
|
342
|
+
"Q": "Q", "Transistor": "Q",
|
|
343
|
+
"U": "U", "IC": "U", "Amplifier": "U",
|
|
344
|
+
"J": "J", "Connector": "J",
|
|
345
|
+
"SW": "SW", "Switch": "SW",
|
|
346
|
+
"F": "F", "Fuse": "F",
|
|
347
|
+
"TP": "TP", "TestPoint": "TP",
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
prefix = ref_prefixes.get(base_ref, "U")
|
|
351
|
+
|
|
352
|
+
# Ensure indexes are current before checking
|
|
353
|
+
self._ensure_indexes_current()
|
|
354
|
+
|
|
355
|
+
# Find next available number
|
|
356
|
+
counter = 1
|
|
357
|
+
while f"{prefix}{counter}" in self._reference_index:
|
|
358
|
+
counter += 1
|
|
359
|
+
|
|
360
|
+
return f"{prefix}{counter}"
|
|
361
|
+
|
|
362
|
+
def _find_available_position(self) -> Point:
|
|
363
|
+
"""
|
|
364
|
+
Find an available position for a new component.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Available position point
|
|
368
|
+
"""
|
|
369
|
+
# Start at a reasonable position and check for conflicts
|
|
370
|
+
base_x, base_y = 100.0, 100.0
|
|
371
|
+
spacing = 25.4 # 1 inch spacing
|
|
372
|
+
|
|
373
|
+
# Check existing positions to avoid overlap
|
|
374
|
+
used_positions = {(comp.position.x, comp.position.y) for comp in self._items}
|
|
375
|
+
|
|
376
|
+
# Find first available position in a grid pattern
|
|
377
|
+
for row in range(10): # Check up to 10 rows
|
|
378
|
+
for col in range(10): # Check up to 10 columns
|
|
379
|
+
x = base_x + col * spacing
|
|
380
|
+
y = base_y + row * spacing
|
|
381
|
+
if (x, y) not in used_positions:
|
|
382
|
+
return Point(x, y)
|
|
383
|
+
|
|
384
|
+
# Fallback to random position if grid is full
|
|
385
|
+
return Point(base_x + len(self._items) * spacing, base_y)
|
|
386
|
+
|
|
387
|
+
def _update_reference_index(self, old_reference: str, new_reference: str) -> None:
|
|
388
|
+
"""
|
|
389
|
+
Update reference index when component reference changes.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
old_reference: Previous reference
|
|
393
|
+
new_reference: New reference
|
|
394
|
+
"""
|
|
395
|
+
if old_reference in self._reference_index:
|
|
396
|
+
component = self._reference_index.pop(old_reference)
|
|
397
|
+
self._reference_index[new_reference] = component
|
|
398
|
+
|
|
399
|
+
# Bulk operations for performance
|
|
400
|
+
def bulk_update(self, criteria: Dict[str, Any], updates: Dict[str, Any]) -> int:
|
|
401
|
+
"""
|
|
402
|
+
Perform bulk update on components matching criteria.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
criteria: Criteria for selecting components
|
|
406
|
+
updates: Updates to apply
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Number of components updated
|
|
410
|
+
"""
|
|
411
|
+
matching_components = self.filter(**criteria)
|
|
412
|
+
|
|
413
|
+
for component in matching_components:
|
|
414
|
+
for attr, value in updates.items():
|
|
415
|
+
if hasattr(component, attr):
|
|
416
|
+
setattr(component, attr, value)
|
|
417
|
+
|
|
418
|
+
self._mark_modified()
|
|
419
|
+
self._mark_indexes_dirty()
|
|
420
|
+
|
|
421
|
+
logger.info(f"Bulk updated {len(matching_components)} components")
|
|
422
|
+
return len(matching_components)
|