kicad-sch-api 0.4.1__py3-none-any.whl → 0.5.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 +67 -2
- kicad_sch_api/cli/kicad_to_python.py +169 -0
- kicad_sch_api/collections/__init__.py +23 -8
- kicad_sch_api/collections/base.py +369 -59
- kicad_sch_api/collections/components.py +1376 -187
- kicad_sch_api/collections/junctions.py +129 -289
- kicad_sch_api/collections/labels.py +391 -287
- kicad_sch_api/collections/wires.py +202 -316
- kicad_sch_api/core/__init__.py +37 -2
- kicad_sch_api/core/component_bounds.py +34 -12
- kicad_sch_api/core/components.py +146 -7
- kicad_sch_api/core/config.py +25 -12
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/element_factory.py +3 -1
- kicad_sch_api/core/formatter.py +24 -7
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/managers/__init__.py +4 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +3 -1
- kicad_sch_api/core/managers/format_sync.py +3 -2
- kicad_sch_api/core/managers/graphics.py +3 -2
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +4 -2
- kicad_sch_api/core/managers/sheet.py +52 -14
- kicad_sch_api/core/managers/text_elements.py +3 -2
- kicad_sch_api/core/managers/validation.py +3 -2
- kicad_sch_api/core/managers/wire.py +112 -54
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +343 -29
- kicad_sch_api/core/types.py +79 -7
- kicad_sch_api/exporters/__init__.py +10 -0
- kicad_sch_api/exporters/python_generator.py +610 -0
- kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
- kicad_sch_api/geometry/__init__.py +15 -3
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/parsers/elements/label_parser.py +30 -8
- kicad_sch_api/parsers/elements/symbol_parser.py +255 -83
- kicad_sch_api/utils/logging.py +555 -0
- kicad_sch_api/utils/logging_decorators.py +587 -0
- kicad_sch_api/utils/validation.py +16 -22
- kicad_sch_api/wrappers/__init__.py +14 -0
- kicad_sch_api/wrappers/base.py +89 -0
- kicad_sch_api/wrappers/wire.py +198 -0
- kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
- kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
- kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
- mcp_server/__init__.py +34 -0
- mcp_server/example_logging_integration.py +506 -0
- mcp_server/models.py +252 -0
- mcp_server/server.py +357 -0
- mcp_server/tools/__init__.py +32 -0
- mcp_server/tools/component_tools.py +516 -0
- mcp_server/tools/connectivity_tools.py +532 -0
- mcp_server/tools/consolidated_tools.py +1216 -0
- mcp_server/tools/pin_discovery.py +333 -0
- mcp_server/utils/__init__.py +38 -0
- mcp_server/utils/logging.py +127 -0
- mcp_server/utils.py +36 -0
- kicad_sch_api-0.4.1.dist-info/METADATA +0 -491
- kicad_sch_api-0.4.1.dist-info/RECORD +0 -87
- kicad_sch_api-0.4.1.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Enhanced wire management with IndexRegistry integration.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
endpoint indexing
|
|
4
|
+
Provides WireCollection using BaseCollection infrastructure with
|
|
5
|
+
endpoint indexing and wire geometry queries.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
@@ -10,397 +10,283 @@ import uuid as uuid_module
|
|
|
10
10
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
12
|
from ..core.types import Point, Wire, WireType
|
|
13
|
-
from .base import
|
|
13
|
+
from .base import BaseCollection, IndexSpec, ValidationLevel
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class WireCollection(
|
|
18
|
+
class WireCollection(BaseCollection[Wire]):
|
|
19
19
|
"""
|
|
20
|
-
|
|
20
|
+
Wire collection with endpoint indexing and geometry queries.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
Inherits from BaseCollection for UUID indexing and adds wire-specific
|
|
23
|
+
functionality for endpoint queries and wire type filtering.
|
|
24
|
+
|
|
25
|
+
Features:
|
|
26
|
+
- Fast UUID lookup via IndexRegistry
|
|
24
27
|
- Multi-point wire support
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
+
- Endpoint-based queries
|
|
29
|
+
- Horizontal/vertical wire detection
|
|
30
|
+
- Lazy index rebuilding
|
|
31
|
+
- Batch mode support
|
|
28
32
|
"""
|
|
29
33
|
|
|
30
|
-
def __init__(
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
wires: Optional[List[Wire]] = None,
|
|
37
|
+
validation_level: ValidationLevel = ValidationLevel.NORMAL,
|
|
38
|
+
):
|
|
31
39
|
"""
|
|
32
40
|
Initialize wire collection.
|
|
33
41
|
|
|
34
42
|
Args:
|
|
35
43
|
wires: Initial list of wires
|
|
44
|
+
validation_level: Validation level for operations
|
|
36
45
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
super().__init__(validation_level=validation_level)
|
|
47
|
+
|
|
48
|
+
# Add initial wires
|
|
49
|
+
if wires:
|
|
50
|
+
with self.batch_mode():
|
|
51
|
+
for wire in wires:
|
|
52
|
+
super().add(wire)
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
logger.debug(f"WireCollection initialized with {len(self)} wires")
|
|
41
55
|
|
|
42
|
-
#
|
|
56
|
+
# BaseCollection abstract method implementations
|
|
43
57
|
def _get_item_uuid(self, item: Wire) -> str:
|
|
44
58
|
"""Extract UUID from wire."""
|
|
45
59
|
return item.uuid
|
|
46
60
|
|
|
47
61
|
def _create_item(self, **kwargs) -> Wire:
|
|
48
|
-
"""Create a new wire
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
62
|
+
"""Create a new wire (not typically used directly)."""
|
|
63
|
+
raise NotImplementedError("Use add() method to create wires")
|
|
64
|
+
|
|
65
|
+
def _get_index_specs(self) -> List[IndexSpec]:
|
|
66
|
+
"""Get index specifications for wire collection."""
|
|
67
|
+
return [
|
|
68
|
+
IndexSpec(
|
|
69
|
+
name="uuid",
|
|
70
|
+
key_func=lambda w: w.uuid,
|
|
71
|
+
unique=True,
|
|
72
|
+
description="UUID index for fast lookups",
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Wire-specific add method
|
|
74
77
|
def add(
|
|
75
78
|
self,
|
|
76
|
-
start: Union[Point, Tuple[float, float]],
|
|
77
|
-
end: Union[Point, Tuple[float, float]],
|
|
78
|
-
|
|
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]]],
|
|
79
|
+
start: Optional[Union[Point, Tuple[float, float]]] = None,
|
|
80
|
+
end: Optional[Union[Point, Tuple[float, float]]] = None,
|
|
81
|
+
points: Optional[List[Union[Point, Tuple[float, float]]]] = None,
|
|
126
82
|
wire_type: WireType = WireType.WIRE,
|
|
127
83
|
stroke_width: float = 0.0,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
) -> Wire:
|
|
84
|
+
uuid: Optional[str] = None,
|
|
85
|
+
) -> str:
|
|
131
86
|
"""
|
|
132
|
-
Add a
|
|
87
|
+
Add a wire to the collection.
|
|
133
88
|
|
|
134
89
|
Args:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
90
|
+
start: Start point (for simple wires)
|
|
91
|
+
end: End point (for simple wires)
|
|
92
|
+
points: List of points (for multi-point wires)
|
|
93
|
+
wire_type: Wire type (wire or bus)
|
|
94
|
+
stroke_width: Line width
|
|
95
|
+
uuid: Optional UUID (auto-generated if not provided)
|
|
140
96
|
|
|
141
97
|
Returns:
|
|
142
|
-
|
|
98
|
+
UUID of the created wire
|
|
143
99
|
|
|
144
100
|
Raises:
|
|
145
|
-
ValueError: If
|
|
101
|
+
ValueError: If neither start/end nor points are provided
|
|
102
|
+
ValueError: If UUID already exists
|
|
146
103
|
"""
|
|
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
104
|
# Generate UUID if not provided
|
|
159
|
-
if
|
|
160
|
-
|
|
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)
|
|
105
|
+
if uuid is None:
|
|
106
|
+
uuid = str(uuid_module.uuid4())
|
|
188
107
|
else:
|
|
189
|
-
|
|
108
|
+
# Check for duplicate
|
|
109
|
+
self._ensure_indexes_current()
|
|
110
|
+
if self._index_registry.has_key("uuid", uuid):
|
|
111
|
+
raise ValueError(f"Wire with UUID '{uuid}' already exists")
|
|
112
|
+
|
|
113
|
+
# Convert points
|
|
114
|
+
wire_points = []
|
|
115
|
+
if points:
|
|
116
|
+
# Multi-point wire
|
|
117
|
+
for point in points:
|
|
118
|
+
if isinstance(point, tuple):
|
|
119
|
+
wire_points.append(Point(point[0], point[1]))
|
|
120
|
+
else:
|
|
121
|
+
wire_points.append(point)
|
|
122
|
+
elif start is not None and end is not None:
|
|
123
|
+
# Simple 2-point wire
|
|
124
|
+
if isinstance(start, tuple):
|
|
125
|
+
start = Point(start[0], start[1])
|
|
126
|
+
if isinstance(end, tuple):
|
|
127
|
+
end = Point(end[0], end[1])
|
|
128
|
+
wire_points = [start, end]
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError("Must provide either start/end points or points list")
|
|
190
131
|
|
|
191
|
-
|
|
132
|
+
# Validate wire has at least 2 points
|
|
133
|
+
if len(wire_points) < 2:
|
|
134
|
+
raise ValueError("Wire must have at least 2 points")
|
|
192
135
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Get all wires of a specific type.
|
|
136
|
+
# Create wire
|
|
137
|
+
wire = Wire(uuid=uuid, points=wire_points, wire_type=wire_type, stroke_width=stroke_width)
|
|
196
138
|
|
|
197
|
-
|
|
198
|
-
|
|
139
|
+
# Add to collection
|
|
140
|
+
super().add(wire)
|
|
199
141
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"""
|
|
203
|
-
self._ensure_indexes_current()
|
|
204
|
-
return self._type_index.get(wire_type, []).copy()
|
|
142
|
+
logger.debug(f"Added wire: {len(wire_points)} points, UUID={uuid}")
|
|
143
|
+
return uuid
|
|
205
144
|
|
|
206
|
-
|
|
145
|
+
# Endpoint-based queries
|
|
146
|
+
def get_by_endpoint(
|
|
147
|
+
self, point: Union[Point, Tuple[float, float]], tolerance: float = 0.01
|
|
148
|
+
) -> List[Wire]:
|
|
207
149
|
"""
|
|
208
|
-
|
|
150
|
+
Find all wires with an endpoint near a given point.
|
|
209
151
|
|
|
210
152
|
Args:
|
|
211
|
-
|
|
153
|
+
point: Point to search for
|
|
154
|
+
tolerance: Distance tolerance for matching
|
|
212
155
|
|
|
213
156
|
Returns:
|
|
214
|
-
List of
|
|
157
|
+
List of wires with endpoint near the point
|
|
215
158
|
"""
|
|
216
|
-
|
|
159
|
+
if isinstance(point, tuple):
|
|
160
|
+
point = Point(point[0], point[1])
|
|
217
161
|
|
|
218
|
-
|
|
219
|
-
for
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
162
|
+
matching_wires = []
|
|
163
|
+
for wire in self._items:
|
|
164
|
+
# Check first and last point (endpoints)
|
|
165
|
+
if (wire.points[0].distance_to(point) <= tolerance or
|
|
166
|
+
wire.points[-1].distance_to(point) <= tolerance):
|
|
167
|
+
matching_wires.append(wire)
|
|
224
168
|
|
|
225
|
-
return
|
|
169
|
+
return matching_wires
|
|
226
170
|
|
|
227
|
-
def
|
|
171
|
+
def get_at_point(
|
|
172
|
+
self, point: Union[Point, Tuple[float, float]], tolerance: float = 0.01
|
|
173
|
+
) -> List[Wire]:
|
|
228
174
|
"""
|
|
229
|
-
Find all
|
|
175
|
+
Find all wires that pass through or near a point.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
point: Point to search for
|
|
179
|
+
tolerance: Distance tolerance for matching
|
|
230
180
|
|
|
231
181
|
Returns:
|
|
232
|
-
List of
|
|
182
|
+
List of wires passing through the point
|
|
233
183
|
"""
|
|
234
|
-
|
|
235
|
-
|
|
184
|
+
if isinstance(point, tuple):
|
|
185
|
+
point = Point(point[0], point[1])
|
|
236
186
|
|
|
187
|
+
matching_wires = []
|
|
237
188
|
for wire in self._items:
|
|
238
|
-
if
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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)
|
|
189
|
+
# Check if any point in wire is near the search point
|
|
190
|
+
for wire_point in wire.points:
|
|
191
|
+
if wire_point.distance_to(point) <= tolerance:
|
|
192
|
+
matching_wires.append(wire)
|
|
193
|
+
break # Found match, move to next wire
|
|
261
194
|
|
|
262
|
-
return
|
|
195
|
+
return matching_wires
|
|
263
196
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
) -> bool:
|
|
197
|
+
# Wire geometry queries
|
|
198
|
+
def get_horizontal(self) -> List[Wire]:
|
|
267
199
|
"""
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Args:
|
|
271
|
-
wire_uuid: UUID of wire to modify
|
|
272
|
-
new_points: New points for the wire path
|
|
200
|
+
Get all horizontal wires (Y coordinates equal).
|
|
273
201
|
|
|
274
202
|
Returns:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
Raises:
|
|
278
|
-
ValueError: If less than 2 points provided
|
|
203
|
+
List of horizontal wires
|
|
279
204
|
"""
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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()
|
|
205
|
+
horizontal = []
|
|
206
|
+
for wire in self._items:
|
|
207
|
+
if len(wire.points) == 2:
|
|
208
|
+
# Simple 2-point wire
|
|
209
|
+
if abs(wire.points[0].y - wire.points[1].y) < 0.01:
|
|
210
|
+
horizontal.append(wire)
|
|
299
211
|
|
|
300
|
-
|
|
301
|
-
return True
|
|
212
|
+
return horizontal
|
|
302
213
|
|
|
303
|
-
|
|
304
|
-
def remove_wires_at_point(self, point: Union[Point, Tuple[float, float]]) -> int:
|
|
214
|
+
def get_vertical(self) -> List[Wire]:
|
|
305
215
|
"""
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
Args:
|
|
309
|
-
point: Point where wires should be removed
|
|
216
|
+
Get all vertical wires (X coordinates equal).
|
|
310
217
|
|
|
311
218
|
Returns:
|
|
312
|
-
|
|
219
|
+
List of vertical wires
|
|
313
220
|
"""
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
221
|
+
vertical = []
|
|
222
|
+
for wire in self._items:
|
|
223
|
+
if len(wire.points) == 2:
|
|
224
|
+
# Simple 2-point wire
|
|
225
|
+
if abs(wire.points[0].x - wire.points[1].x) < 0.01:
|
|
226
|
+
vertical.append(wire)
|
|
318
227
|
|
|
319
|
-
|
|
320
|
-
return len(wires_to_remove)
|
|
228
|
+
return vertical
|
|
321
229
|
|
|
322
|
-
def
|
|
323
|
-
self,
|
|
324
|
-
wire_type: Optional[WireType] = None,
|
|
325
|
-
stroke_width: Optional[float] = None,
|
|
326
|
-
stroke_type: Optional[str] = None,
|
|
327
|
-
) -> int:
|
|
230
|
+
def get_by_type(self, wire_type: WireType) -> List[Wire]:
|
|
328
231
|
"""
|
|
329
|
-
|
|
232
|
+
Get all wires of a specific type.
|
|
330
233
|
|
|
331
234
|
Args:
|
|
332
|
-
wire_type:
|
|
333
|
-
stroke_width: New stroke width (None to keep existing)
|
|
334
|
-
stroke_type: New stroke type (None to keep existing)
|
|
235
|
+
wire_type: Wire type to filter by
|
|
335
236
|
|
|
336
237
|
Returns:
|
|
337
|
-
|
|
238
|
+
List of wires matching the type
|
|
338
239
|
"""
|
|
339
|
-
|
|
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()
|
|
240
|
+
return [w for w in self._items if w.wire_type == wire_type]
|
|
354
241
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# Collection statistics
|
|
359
|
-
def get_connection_statistics(self) -> Dict[str, Any]:
|
|
242
|
+
# Statistics
|
|
243
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
360
244
|
"""
|
|
361
|
-
Get
|
|
245
|
+
Get wire collection statistics.
|
|
362
246
|
|
|
363
247
|
Returns:
|
|
364
|
-
Dictionary with
|
|
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:
|
|
248
|
+
Dictionary with wire statistics
|
|
383
249
|
"""
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
250
|
+
if not self._items:
|
|
251
|
+
base_stats = super().get_statistics()
|
|
252
|
+
base_stats.update({
|
|
253
|
+
"total_wires": 0,
|
|
254
|
+
"total_segments": 0,
|
|
255
|
+
"wire_count": 0,
|
|
256
|
+
"bus_count": 0,
|
|
257
|
+
"horizontal_count": 0,
|
|
258
|
+
"vertical_count": 0,
|
|
259
|
+
"avg_points_per_wire": 0,
|
|
260
|
+
})
|
|
261
|
+
return base_stats
|
|
262
|
+
|
|
263
|
+
wire_count = sum(1 for w in self._items if w.wire_type == WireType.WIRE)
|
|
264
|
+
bus_count = sum(1 for w in self._items if w.wire_type == WireType.BUS)
|
|
265
|
+
total_segments = sum(len(w.points) - 1 for w in self._items)
|
|
266
|
+
avg_points = sum(len(w.points) for w in self._items) / len(self._items)
|
|
267
|
+
|
|
268
|
+
horizontal = len(self.get_horizontal())
|
|
269
|
+
vertical = len(self.get_vertical())
|
|
270
|
+
|
|
271
|
+
base_stats = super().get_statistics()
|
|
272
|
+
base_stats.update({
|
|
273
|
+
"total_wires": len(self._items),
|
|
274
|
+
"total_segments": total_segments,
|
|
275
|
+
"wire_count": wire_count,
|
|
276
|
+
"bus_count": bus_count,
|
|
277
|
+
"horizontal_count": horizontal,
|
|
278
|
+
"vertical_count": vertical,
|
|
279
|
+
"avg_points_per_wire": avg_points,
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
return base_stats
|
|
283
|
+
|
|
284
|
+
# Compatibility methods
|
|
285
|
+
@property
|
|
286
|
+
def modified(self) -> bool:
|
|
287
|
+
"""Check if collection has been modified (compatibility)."""
|
|
288
|
+
return self.is_modified
|
|
289
|
+
|
|
290
|
+
def mark_saved(self) -> None:
|
|
291
|
+
"""Mark collection as saved (reset modified flag)."""
|
|
292
|
+
self.mark_clean()
|
kicad_sch_api/core/__init__.py
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
"""Core kicad-sch-api functionality."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from ..collections import Component, ComponentCollection
|
|
4
4
|
from .formatter import ExactFormatter
|
|
5
5
|
from .parser import SExpressionParser
|
|
6
6
|
from .schematic import Schematic, create_schematic, load_schematic
|
|
7
|
-
from .types import Junction, Label, Net, Point, SchematicSymbol, Wire
|
|
7
|
+
from .types import Junction, Label, Net, PinInfo, Point, SchematicSymbol, Wire
|
|
8
|
+
# Exception hierarchy
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
KiCadSchError,
|
|
11
|
+
ValidationError,
|
|
12
|
+
ReferenceError,
|
|
13
|
+
LibraryError,
|
|
14
|
+
GeometryError,
|
|
15
|
+
NetError,
|
|
16
|
+
ParseError,
|
|
17
|
+
FormatError,
|
|
18
|
+
CollectionError,
|
|
19
|
+
ElementNotFoundError,
|
|
20
|
+
DuplicateElementError,
|
|
21
|
+
CollectionOperationError,
|
|
22
|
+
FileOperationError,
|
|
23
|
+
CLIError,
|
|
24
|
+
SchematicStateError,
|
|
25
|
+
)
|
|
8
26
|
|
|
9
27
|
__all__ = [
|
|
10
28
|
"Schematic",
|
|
@@ -16,8 +34,25 @@ __all__ = [
|
|
|
16
34
|
"Junction",
|
|
17
35
|
"Label",
|
|
18
36
|
"Net",
|
|
37
|
+
"PinInfo",
|
|
19
38
|
"SExpressionParser",
|
|
20
39
|
"ExactFormatter",
|
|
21
40
|
"load_schematic",
|
|
22
41
|
"create_schematic",
|
|
42
|
+
# Exceptions
|
|
43
|
+
"KiCadSchError",
|
|
44
|
+
"ValidationError",
|
|
45
|
+
"ReferenceError",
|
|
46
|
+
"LibraryError",
|
|
47
|
+
"GeometryError",
|
|
48
|
+
"NetError",
|
|
49
|
+
"ParseError",
|
|
50
|
+
"FormatError",
|
|
51
|
+
"CollectionError",
|
|
52
|
+
"ElementNotFoundError",
|
|
53
|
+
"DuplicateElementError",
|
|
54
|
+
"CollectionOperationError",
|
|
55
|
+
"FileOperationError",
|
|
56
|
+
"CLIError",
|
|
57
|
+
"SchematicStateError",
|
|
23
58
|
]
|