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 junction management with IndexRegistry integration.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
position-based queries and
|
|
4
|
+
Provides JunctionCollection using BaseCollection infrastructure with
|
|
5
|
+
position-based queries and validation.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
@@ -10,357 +10,197 @@ import uuid as uuid_module
|
|
|
10
10
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
12
|
from ..core.types import Junction, Point
|
|
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 JunctionCollection(
|
|
18
|
+
class JunctionCollection(BaseCollection[Junction]):
|
|
19
19
|
"""
|
|
20
|
-
|
|
20
|
+
Junction collection with position-based queries.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
22
|
+
Inherits from BaseCollection for UUID indexing and adds junction-specific
|
|
23
|
+
position-based search capabilities.
|
|
24
|
+
|
|
25
|
+
Features:
|
|
26
|
+
- Fast UUID lookup via IndexRegistry
|
|
27
|
+
- Position-based junction queries
|
|
28
|
+
- Lazy index rebuilding
|
|
29
|
+
- Batch mode support
|
|
27
30
|
"""
|
|
28
31
|
|
|
29
|
-
def __init__(
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
junctions: Optional[List[Junction]] = None,
|
|
35
|
+
validation_level: ValidationLevel = ValidationLevel.NORMAL,
|
|
36
|
+
):
|
|
30
37
|
"""
|
|
31
38
|
Initialize junction collection.
|
|
32
39
|
|
|
33
40
|
Args:
|
|
34
41
|
junctions: Initial list of junctions
|
|
42
|
+
validation_level: Validation level for operations
|
|
35
43
|
"""
|
|
36
|
-
|
|
44
|
+
super().__init__(validation_level=validation_level)
|
|
45
|
+
|
|
46
|
+
# Add initial junctions
|
|
47
|
+
if junctions:
|
|
48
|
+
with self.batch_mode():
|
|
49
|
+
for junction in junctions:
|
|
50
|
+
super().add(junction)
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
logger.debug(f"JunctionCollection initialized with {len(self)} junctions")
|
|
39
53
|
|
|
40
|
-
#
|
|
54
|
+
# BaseCollection abstract method implementations
|
|
41
55
|
def _get_item_uuid(self, item: Junction) -> str:
|
|
42
56
|
"""Extract UUID from junction."""
|
|
43
57
|
return item.uuid
|
|
44
58
|
|
|
45
59
|
def _create_item(self, **kwargs) -> Junction:
|
|
46
|
-
"""Create a new junction
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
self._position_index[pos_key] = junction
|
|
62
|
-
|
|
63
|
-
# Junction-specific methods
|
|
60
|
+
"""Create a new junction (not typically used directly)."""
|
|
61
|
+
raise NotImplementedError("Use add() method to create junctions")
|
|
62
|
+
|
|
63
|
+
def _get_index_specs(self) -> List[IndexSpec]:
|
|
64
|
+
"""Get index specifications for junction collection."""
|
|
65
|
+
return [
|
|
66
|
+
IndexSpec(
|
|
67
|
+
name="uuid",
|
|
68
|
+
key_func=lambda j: j.uuid,
|
|
69
|
+
unique=True,
|
|
70
|
+
description="UUID index for fast lookups",
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# Junction-specific add method
|
|
64
75
|
def add(
|
|
65
76
|
self,
|
|
66
77
|
position: Union[Point, Tuple[float, float]],
|
|
67
|
-
diameter: float =
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
diameter: float = 0,
|
|
79
|
+
color: Tuple[int, int, int, int] = (0, 0, 0, 0),
|
|
80
|
+
uuid: Optional[str] = None,
|
|
81
|
+
) -> str:
|
|
70
82
|
"""
|
|
71
|
-
Add a
|
|
83
|
+
Add a junction to the collection.
|
|
72
84
|
|
|
73
85
|
Args:
|
|
74
86
|
position: Junction position
|
|
75
|
-
diameter: Junction diameter (
|
|
76
|
-
|
|
87
|
+
diameter: Junction diameter (0 is KiCAD default)
|
|
88
|
+
color: RGBA color tuple (0,0,0,0 is default)
|
|
89
|
+
uuid: Optional UUID (auto-generated if not provided)
|
|
77
90
|
|
|
78
91
|
Returns:
|
|
79
|
-
|
|
92
|
+
UUID of the created junction
|
|
80
93
|
|
|
81
94
|
Raises:
|
|
82
|
-
ValueError: If
|
|
95
|
+
ValueError: If UUID already exists
|
|
83
96
|
"""
|
|
84
|
-
#
|
|
97
|
+
# Generate UUID if not provided
|
|
98
|
+
if uuid is None:
|
|
99
|
+
uuid = str(uuid_module.uuid4())
|
|
100
|
+
else:
|
|
101
|
+
# Check for duplicate
|
|
102
|
+
self._ensure_indexes_current()
|
|
103
|
+
if self._index_registry.has_key("uuid", uuid):
|
|
104
|
+
raise ValueError(f"Junction with UUID '{uuid}' already exists")
|
|
105
|
+
|
|
106
|
+
# Convert position
|
|
85
107
|
if isinstance(position, tuple):
|
|
86
108
|
position = Point(position[0], position[1])
|
|
87
109
|
|
|
88
|
-
# Check for existing junction at position
|
|
89
|
-
pos_key = (position.x, position.y)
|
|
90
|
-
if pos_key in self._position_index:
|
|
91
|
-
existing = self._position_index[pos_key]
|
|
92
|
-
raise ValueError(
|
|
93
|
-
f"Junction already exists at position {position} (UUID: {existing.uuid})"
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
# Generate UUID if not provided
|
|
97
|
-
if junction_uuid is None:
|
|
98
|
-
junction_uuid = str(uuid_module.uuid4())
|
|
99
|
-
|
|
100
110
|
# Create junction
|
|
101
|
-
junction = Junction(uuid=
|
|
111
|
+
junction = Junction(uuid=uuid, position=position, diameter=diameter, color=color)
|
|
112
|
+
|
|
113
|
+
# Add to collection
|
|
114
|
+
super().add(junction)
|
|
102
115
|
|
|
103
|
-
|
|
104
|
-
return
|
|
116
|
+
logger.debug(f"Added junction at {position}, UUID={uuid}")
|
|
117
|
+
return uuid
|
|
105
118
|
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
# Position-based queries
|
|
120
|
+
def get_at_position(
|
|
121
|
+
self, position: Union[Point, Tuple[float, float]], tolerance: float = 0.01
|
|
108
122
|
) -> Optional[Junction]:
|
|
109
123
|
"""
|
|
110
|
-
|
|
124
|
+
Find junction at or near a specific position.
|
|
111
125
|
|
|
112
126
|
Args:
|
|
113
|
-
position: Position to search
|
|
114
|
-
tolerance:
|
|
127
|
+
position: Position to search
|
|
128
|
+
tolerance: Distance tolerance for matching
|
|
115
129
|
|
|
116
130
|
Returns:
|
|
117
131
|
Junction if found, None otherwise
|
|
118
132
|
"""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if isinstance(position, Point):
|
|
122
|
-
pos_key = (position.x, position.y)
|
|
123
|
-
else:
|
|
124
|
-
pos_key = position
|
|
125
|
-
|
|
126
|
-
if tolerance == 0.0:
|
|
127
|
-
# Exact match
|
|
128
|
-
return self._position_index.get(pos_key)
|
|
129
|
-
else:
|
|
130
|
-
# Tolerance-based search
|
|
131
|
-
target_x, target_y = pos_key
|
|
132
|
-
|
|
133
|
-
for junction in self._items:
|
|
134
|
-
dx = abs(junction.position.x - target_x)
|
|
135
|
-
dy = abs(junction.position.y - target_y)
|
|
136
|
-
distance = (dx**2 + dy**2) ** 0.5
|
|
137
|
-
|
|
138
|
-
if distance <= tolerance:
|
|
139
|
-
return junction
|
|
140
|
-
|
|
141
|
-
return None
|
|
142
|
-
|
|
143
|
-
def has_junction_at_position(
|
|
144
|
-
self, position: Union[Point, Tuple[float, float]], tolerance: float = 0.0
|
|
145
|
-
) -> bool:
|
|
146
|
-
"""
|
|
147
|
-
Check if a junction exists at a specific position.
|
|
133
|
+
if isinstance(position, tuple):
|
|
134
|
+
position = Point(position[0], position[1])
|
|
148
135
|
|
|
149
|
-
|
|
150
|
-
position
|
|
151
|
-
|
|
136
|
+
for junction in self._items:
|
|
137
|
+
if junction.position.distance_to(position) <= tolerance:
|
|
138
|
+
return junction
|
|
152
139
|
|
|
153
|
-
|
|
154
|
-
True if junction exists at position
|
|
155
|
-
"""
|
|
156
|
-
return self.get_junction_at_position(position, tolerance) is not None
|
|
140
|
+
return None
|
|
157
141
|
|
|
158
|
-
def
|
|
159
|
-
self,
|
|
142
|
+
def get_by_point(
|
|
143
|
+
self, point: Union[Point, Tuple[float, float]], tolerance: float = 0.01
|
|
160
144
|
) -> List[Junction]:
|
|
161
145
|
"""
|
|
162
|
-
Find all junctions
|
|
146
|
+
Find all junctions near a point.
|
|
163
147
|
|
|
164
148
|
Args:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
max_x: Maximum X coordinate
|
|
168
|
-
max_y: Maximum Y coordinate
|
|
149
|
+
point: Point to search near
|
|
150
|
+
tolerance: Distance tolerance
|
|
169
151
|
|
|
170
152
|
Returns:
|
|
171
|
-
List of junctions
|
|
153
|
+
List of junctions near the point
|
|
172
154
|
"""
|
|
173
|
-
|
|
155
|
+
if isinstance(point, tuple):
|
|
156
|
+
point = Point(point[0], point[1])
|
|
174
157
|
|
|
158
|
+
matching_junctions = []
|
|
175
159
|
for junction in self._items:
|
|
176
|
-
if
|
|
160
|
+
if junction.position.distance_to(point) <= tolerance:
|
|
177
161
|
matching_junctions.append(junction)
|
|
178
162
|
|
|
179
163
|
return matching_junctions
|
|
180
164
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
) -> bool:
|
|
184
|
-
"""
|
|
185
|
-
Update the position of an existing junction.
|
|
186
|
-
|
|
187
|
-
Args:
|
|
188
|
-
junction_uuid: UUID of junction to update
|
|
189
|
-
new_position: New position
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
True if junction was updated, False if not found
|
|
193
|
-
|
|
194
|
-
Raises:
|
|
195
|
-
ValueError: If another junction exists at new position
|
|
196
|
-
"""
|
|
197
|
-
junction = self.get(junction_uuid)
|
|
198
|
-
if not junction:
|
|
199
|
-
return False
|
|
200
|
-
|
|
201
|
-
# Convert tuple to Point if needed
|
|
202
|
-
if isinstance(new_position, tuple):
|
|
203
|
-
new_position = Point(new_position[0], new_position[1])
|
|
204
|
-
|
|
205
|
-
# Check for existing junction at new position
|
|
206
|
-
new_pos_key = (new_position.x, new_position.y)
|
|
207
|
-
if new_pos_key in self._position_index:
|
|
208
|
-
existing = self._position_index[new_pos_key]
|
|
209
|
-
if existing.uuid != junction_uuid:
|
|
210
|
-
raise ValueError(f"Junction already exists at position {new_position}")
|
|
211
|
-
|
|
212
|
-
# Update position
|
|
213
|
-
junction.position = new_position
|
|
214
|
-
self._mark_modified()
|
|
215
|
-
self._mark_indexes_dirty()
|
|
216
|
-
|
|
217
|
-
logger.debug(f"Updated junction {junction_uuid} position to {new_position}")
|
|
218
|
-
return True
|
|
219
|
-
|
|
220
|
-
def update_junction_diameter(self, junction_uuid: str, new_diameter: float) -> bool:
|
|
221
|
-
"""
|
|
222
|
-
Update the diameter of an existing junction.
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
junction_uuid: UUID of junction to update
|
|
226
|
-
new_diameter: New diameter
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
True if junction was updated, False if not found
|
|
230
|
-
|
|
231
|
-
Raises:
|
|
232
|
-
ValueError: If diameter is not positive
|
|
233
|
-
"""
|
|
234
|
-
if new_diameter <= 0:
|
|
235
|
-
raise ValueError("Junction diameter must be positive")
|
|
236
|
-
|
|
237
|
-
junction = self.get(junction_uuid)
|
|
238
|
-
if not junction:
|
|
239
|
-
return False
|
|
240
|
-
|
|
241
|
-
# Update diameter
|
|
242
|
-
junction.diameter = new_diameter
|
|
243
|
-
self._mark_modified()
|
|
244
|
-
|
|
245
|
-
logger.debug(f"Updated junction {junction_uuid} diameter to {new_diameter}")
|
|
246
|
-
return True
|
|
247
|
-
|
|
248
|
-
def get_junction_positions(self) -> List[Point]:
|
|
249
|
-
"""
|
|
250
|
-
Get all junction positions.
|
|
251
|
-
|
|
252
|
-
Returns:
|
|
253
|
-
List of all junction positions
|
|
254
|
-
"""
|
|
255
|
-
return [junction.position for junction in self._items]
|
|
256
|
-
|
|
257
|
-
def validate_grid_alignment(self, grid_size: float = 1.27) -> List[Junction]:
|
|
258
|
-
"""
|
|
259
|
-
Find junctions that are not aligned to the specified grid.
|
|
260
|
-
|
|
261
|
-
Args:
|
|
262
|
-
grid_size: Grid size for alignment check (default KiCAD standard)
|
|
263
|
-
|
|
264
|
-
Returns:
|
|
265
|
-
List of junctions not aligned to grid
|
|
266
|
-
"""
|
|
267
|
-
misaligned = []
|
|
268
|
-
|
|
269
|
-
for junction in self._items:
|
|
270
|
-
# Check if position is aligned to grid
|
|
271
|
-
x_remainder = junction.position.x % grid_size
|
|
272
|
-
y_remainder = junction.position.y % grid_size
|
|
273
|
-
|
|
274
|
-
# Allow small tolerance for floating point precision
|
|
275
|
-
tolerance = grid_size * 0.01
|
|
276
|
-
if (x_remainder > tolerance and x_remainder < grid_size - tolerance) or (
|
|
277
|
-
y_remainder > tolerance and y_remainder < grid_size - tolerance
|
|
278
|
-
):
|
|
279
|
-
misaligned.append(junction)
|
|
280
|
-
|
|
281
|
-
return misaligned
|
|
282
|
-
|
|
283
|
-
def snap_to_grid(self, grid_size: float = 1.27) -> int:
|
|
284
|
-
"""
|
|
285
|
-
Snap all junctions to the specified grid.
|
|
286
|
-
|
|
287
|
-
Args:
|
|
288
|
-
grid_size: Grid size for snapping (default KiCAD standard)
|
|
289
|
-
|
|
290
|
-
Returns:
|
|
291
|
-
Number of junctions that were moved
|
|
292
|
-
"""
|
|
293
|
-
moved_count = 0
|
|
294
|
-
|
|
295
|
-
for junction in self._items:
|
|
296
|
-
# Calculate grid-aligned position
|
|
297
|
-
aligned_x = round(junction.position.x / grid_size) * grid_size
|
|
298
|
-
aligned_y = round(junction.position.y / grid_size) * grid_size
|
|
299
|
-
|
|
300
|
-
# Check if position needs to change
|
|
301
|
-
if (
|
|
302
|
-
abs(junction.position.x - aligned_x) > 0.001
|
|
303
|
-
or abs(junction.position.y - aligned_y) > 0.001
|
|
304
|
-
):
|
|
305
|
-
|
|
306
|
-
# Update position
|
|
307
|
-
junction.position = Point(aligned_x, aligned_y)
|
|
308
|
-
moved_count += 1
|
|
309
|
-
|
|
310
|
-
if moved_count > 0:
|
|
311
|
-
self._mark_modified()
|
|
312
|
-
self._mark_indexes_dirty()
|
|
313
|
-
|
|
314
|
-
logger.info(f"Snapped {moved_count} junctions to {grid_size}mm grid")
|
|
315
|
-
return moved_count
|
|
316
|
-
|
|
317
|
-
# Bulk operations
|
|
318
|
-
def remove_junctions_in_region(
|
|
319
|
-
self, min_x: float, min_y: float, max_x: float, max_y: float
|
|
320
|
-
) -> int:
|
|
321
|
-
"""
|
|
322
|
-
Remove all junctions within a rectangular region.
|
|
323
|
-
|
|
324
|
-
Args:
|
|
325
|
-
min_x: Minimum X coordinate
|
|
326
|
-
min_y: Minimum Y coordinate
|
|
327
|
-
max_x: Maximum X coordinate
|
|
328
|
-
max_y: Maximum Y coordinate
|
|
329
|
-
|
|
330
|
-
Returns:
|
|
331
|
-
Number of junctions removed
|
|
332
|
-
"""
|
|
333
|
-
junctions_to_remove = self.find_junctions_in_region(min_x, min_y, max_x, max_y)
|
|
334
|
-
|
|
335
|
-
for junction in junctions_to_remove:
|
|
336
|
-
self.remove(junction.uuid)
|
|
337
|
-
|
|
338
|
-
logger.info(f"Removed {len(junctions_to_remove)} junctions in region")
|
|
339
|
-
return len(junctions_to_remove)
|
|
340
|
-
|
|
341
|
-
# Collection statistics
|
|
342
|
-
def get_junction_statistics(self) -> Dict[str, Any]:
|
|
165
|
+
# Statistics
|
|
166
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
343
167
|
"""
|
|
344
|
-
Get junction statistics
|
|
168
|
+
Get junction collection statistics.
|
|
345
169
|
|
|
346
170
|
Returns:
|
|
347
171
|
Dictionary with junction statistics
|
|
348
172
|
"""
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
173
|
+
if not self._items:
|
|
174
|
+
base_stats = super().get_statistics()
|
|
175
|
+
base_stats.update({
|
|
176
|
+
"total_junctions": 0,
|
|
177
|
+
"avg_diameter": 0,
|
|
178
|
+
"positions": [],
|
|
179
|
+
"unique_diameters": 0,
|
|
180
|
+
"unique_colors": 0,
|
|
181
|
+
})
|
|
182
|
+
return base_stats
|
|
183
|
+
|
|
184
|
+
avg_diameter = sum(j.diameter for j in self._items) / len(self._items)
|
|
185
|
+
positions = [(j.position.x, j.position.y) for j in self._items]
|
|
186
|
+
|
|
187
|
+
base_stats = super().get_statistics()
|
|
188
|
+
base_stats.update({
|
|
189
|
+
"total_junctions": len(self._items),
|
|
190
|
+
"avg_diameter": avg_diameter,
|
|
191
|
+
"positions": positions,
|
|
192
|
+
"unique_diameters": len(set(j.diameter for j in self._items)),
|
|
193
|
+
"unique_colors": len(set(j.color for j in self._items)),
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return base_stats
|
|
197
|
+
|
|
198
|
+
# Compatibility methods
|
|
199
|
+
@property
|
|
200
|
+
def modified(self) -> bool:
|
|
201
|
+
"""Check if collection has been modified (compatibility)."""
|
|
202
|
+
return self.is_modified
|
|
203
|
+
|
|
204
|
+
def mark_saved(self) -> None:
|
|
205
|
+
"""Mark collection as saved (reset modified flag)."""
|
|
206
|
+
self.mark_clean()
|