kicad-sch-api 0.3.5__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 +2 -2
- kicad_sch_api/collections/base.py +5 -7
- kicad_sch_api/collections/components.py +24 -12
- kicad_sch_api/collections/junctions.py +31 -43
- kicad_sch_api/collections/labels.py +19 -27
- kicad_sch_api/collections/wires.py +17 -18
- kicad_sch_api/core/components.py +5 -0
- kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api/core/labels.py +2 -2
- 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 +1 -1
- kicad_sch_api/core/no_connects.py +5 -3
- kicad_sch_api/core/parser.py +75 -41
- kicad_sch_api/core/schematic.py +779 -1083
- kicad_sch_api/core/texts.py +1 -1
- kicad_sch_api/core/types.py +1 -4
- kicad_sch_api/geometry/font_metrics.py +3 -1
- kicad_sch_api/geometry/symbol_bbox.py +40 -21
- kicad_sch_api/interfaces/__init__.py +1 -1
- kicad_sch_api/interfaces/parser.py +1 -1
- kicad_sch_api/interfaces/repository.py +1 -1
- kicad_sch_api/interfaces/resolver.py +1 -1
- kicad_sch_api/parsers/__init__.py +2 -2
- kicad_sch_api/parsers/base.py +7 -10
- kicad_sch_api/parsers/label_parser.py +7 -7
- kicad_sch_api/parsers/registry.py +4 -2
- kicad_sch_api/parsers/symbol_parser.py +5 -10
- kicad_sch_api/parsers/wire_parser.py +2 -2
- kicad_sch_api/symbols/__init__.py +1 -1
- kicad_sch_api/symbols/cache.py +9 -12
- kicad_sch_api/symbols/resolver.py +20 -26
- kicad_sch_api/symbols/validators.py +188 -137
- {kicad_sch_api-0.3.5.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.5.dist-info/RECORD +0 -58
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Format Synchronization Manager for KiCAD schematic data consistency.
|
|
3
|
+
|
|
4
|
+
Handles bidirectional synchronization between Python object models and
|
|
5
|
+
raw S-expression data structures while maintaining exact format preservation
|
|
6
|
+
and tracking changes for efficient updates.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
|
11
|
+
|
|
12
|
+
from ..components import Component
|
|
13
|
+
from ..types import Point, Wire
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FormatSyncManager:
|
|
19
|
+
"""
|
|
20
|
+
Manages synchronization between object models and S-expression data.
|
|
21
|
+
|
|
22
|
+
Responsible for:
|
|
23
|
+
- Bidirectional data synchronization
|
|
24
|
+
- Change tracking and dirty flag management
|
|
25
|
+
- Format preservation during updates
|
|
26
|
+
- Incremental update optimization
|
|
27
|
+
- Data consistency validation
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, schematic_data: Dict[str, Any]):
|
|
31
|
+
"""
|
|
32
|
+
Initialize FormatSyncManager.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
schematic_data: Reference to schematic data
|
|
36
|
+
"""
|
|
37
|
+
self._data = schematic_data
|
|
38
|
+
self._dirty_flags: Set[str] = set()
|
|
39
|
+
self._change_log: List[Dict[str, Any]] = []
|
|
40
|
+
self._sync_lock = False
|
|
41
|
+
|
|
42
|
+
def mark_dirty(
|
|
43
|
+
self, section: str, operation: str = "update", context: Optional[Dict] = None
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Mark a data section as dirty for synchronization.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
section: Data section that changed (e.g., 'components', 'wires')
|
|
50
|
+
operation: Type of operation (update, add, remove)
|
|
51
|
+
context: Additional context about the change
|
|
52
|
+
"""
|
|
53
|
+
if self._sync_lock:
|
|
54
|
+
logger.debug(f"Sync locked, deferring dirty mark for {section}")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
self._dirty_flags.add(section)
|
|
58
|
+
|
|
59
|
+
change_entry = {
|
|
60
|
+
"section": section,
|
|
61
|
+
"operation": operation,
|
|
62
|
+
"timestamp": None, # Would use datetime in real implementation
|
|
63
|
+
"context": context or {},
|
|
64
|
+
}
|
|
65
|
+
self._change_log.append(change_entry)
|
|
66
|
+
|
|
67
|
+
logger.debug(f"Marked section '{section}' as dirty ({operation})")
|
|
68
|
+
|
|
69
|
+
def sync_component_to_data(self, component: Component) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Synchronize a component object back to S-expression data.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
component: Component to sync
|
|
75
|
+
"""
|
|
76
|
+
symbols = self._data.get("symbol", [])
|
|
77
|
+
|
|
78
|
+
# Find the corresponding symbol entry
|
|
79
|
+
for symbol_data in symbols:
|
|
80
|
+
if symbol_data.get("uuid") == component.uuid:
|
|
81
|
+
self._update_symbol_data_from_component(symbol_data, component)
|
|
82
|
+
self.mark_dirty("symbol", "update", {"uuid": component.uuid})
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
logger.warning(f"Component not found in data for sync: {component.uuid}")
|
|
86
|
+
|
|
87
|
+
def sync_component_from_data(self, component: Component, symbol_data: Dict[str, Any]) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Update component object from S-expression data.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
component: Component to update
|
|
93
|
+
symbol_data: Source symbol data
|
|
94
|
+
"""
|
|
95
|
+
# Update component properties from symbol data
|
|
96
|
+
if "at" in symbol_data:
|
|
97
|
+
at_data = symbol_data["at"]
|
|
98
|
+
component.position = Point(at_data[0], at_data[1])
|
|
99
|
+
if len(at_data) > 2:
|
|
100
|
+
component.rotation = at_data[2]
|
|
101
|
+
|
|
102
|
+
# Update properties
|
|
103
|
+
if "property" in symbol_data:
|
|
104
|
+
for prop in symbol_data["property"]:
|
|
105
|
+
name = prop.get("name")
|
|
106
|
+
value = prop.get("value")
|
|
107
|
+
if name and value is not None:
|
|
108
|
+
component.set_property(name, value)
|
|
109
|
+
|
|
110
|
+
logger.debug(f"Synced component from data: {component.uuid}")
|
|
111
|
+
|
|
112
|
+
def sync_wire_to_data(self, wire: Wire) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Synchronize a wire object back to S-expression data.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
wire: Wire to sync
|
|
118
|
+
"""
|
|
119
|
+
wires = self._data.get("wire", [])
|
|
120
|
+
|
|
121
|
+
# Find the corresponding wire entry
|
|
122
|
+
for wire_data in wires:
|
|
123
|
+
if wire_data.get("uuid") == wire.uuid:
|
|
124
|
+
self._update_wire_data_from_object(wire_data, wire)
|
|
125
|
+
self.mark_dirty("wire", "update", {"uuid": wire.uuid})
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
logger.warning(f"Wire not found in data for sync: {wire.uuid}")
|
|
129
|
+
|
|
130
|
+
def sync_wire_from_data(self, wire: Wire, wire_data: Dict[str, Any]) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Update wire object from S-expression data.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
wire: Wire to update
|
|
136
|
+
wire_data: Source wire data
|
|
137
|
+
"""
|
|
138
|
+
# Update wire endpoints
|
|
139
|
+
if "pts" in wire_data:
|
|
140
|
+
pts = wire_data["pts"]
|
|
141
|
+
if len(pts) >= 2:
|
|
142
|
+
start_pt = pts[0]
|
|
143
|
+
end_pt = pts[-1]
|
|
144
|
+
wire.start = Point(start_pt["xy"][0], start_pt["xy"][1])
|
|
145
|
+
wire.end = Point(end_pt["xy"][0], end_pt["xy"][1])
|
|
146
|
+
|
|
147
|
+
# Update stroke properties
|
|
148
|
+
if "stroke" in wire_data:
|
|
149
|
+
wire.stroke_width = wire_data["stroke"].get("width", 0.0)
|
|
150
|
+
|
|
151
|
+
logger.debug(f"Synced wire from data: {wire.uuid}")
|
|
152
|
+
|
|
153
|
+
def sync_all_to_data(self, component_collection=None, wire_collection=None) -> None:
|
|
154
|
+
"""
|
|
155
|
+
Perform full synchronization of all objects to S-expression data.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
component_collection: Collection of components to sync
|
|
159
|
+
wire_collection: Collection of wires to sync
|
|
160
|
+
"""
|
|
161
|
+
self._sync_lock = True
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# Sync components
|
|
165
|
+
if component_collection:
|
|
166
|
+
for component in component_collection:
|
|
167
|
+
self.sync_component_to_data(component)
|
|
168
|
+
|
|
169
|
+
# Sync wires
|
|
170
|
+
if wire_collection:
|
|
171
|
+
for wire in wire_collection:
|
|
172
|
+
self.sync_wire_to_data(wire)
|
|
173
|
+
|
|
174
|
+
# Clear dirty flags after successful sync
|
|
175
|
+
self._dirty_flags.clear()
|
|
176
|
+
logger.info("Full synchronization to data completed")
|
|
177
|
+
|
|
178
|
+
finally:
|
|
179
|
+
self._sync_lock = False
|
|
180
|
+
|
|
181
|
+
def sync_all_from_data(self, component_collection=None, wire_collection=None) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Perform full synchronization from S-expression data to objects.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
component_collection: Collection of components to update
|
|
187
|
+
wire_collection: Collection of wires to update
|
|
188
|
+
"""
|
|
189
|
+
self._sync_lock = True
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
# Sync components from symbols
|
|
193
|
+
if component_collection:
|
|
194
|
+
symbols = self._data.get("symbol", [])
|
|
195
|
+
for symbol_data in symbols:
|
|
196
|
+
uuid = symbol_data.get("uuid")
|
|
197
|
+
if uuid:
|
|
198
|
+
component = component_collection.get_by_uuid(uuid)
|
|
199
|
+
if component:
|
|
200
|
+
self.sync_component_from_data(component, symbol_data)
|
|
201
|
+
|
|
202
|
+
# Sync wires
|
|
203
|
+
if wire_collection:
|
|
204
|
+
wires = self._data.get("wire", [])
|
|
205
|
+
for wire_data in wires:
|
|
206
|
+
uuid = wire_data.get("uuid")
|
|
207
|
+
if uuid:
|
|
208
|
+
wire = wire_collection.get_by_uuid(uuid)
|
|
209
|
+
if wire:
|
|
210
|
+
self.sync_wire_from_data(wire, wire_data)
|
|
211
|
+
|
|
212
|
+
logger.info("Full synchronization from data completed")
|
|
213
|
+
|
|
214
|
+
finally:
|
|
215
|
+
self._sync_lock = False
|
|
216
|
+
|
|
217
|
+
def perform_incremental_sync(self, component_collection=None, wire_collection=None) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Perform incremental synchronization of only dirty sections.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
component_collection: Collection of components
|
|
223
|
+
wire_collection: Collection of wires
|
|
224
|
+
"""
|
|
225
|
+
if not self._dirty_flags:
|
|
226
|
+
logger.debug("No dirty sections, skipping sync")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
self._sync_lock = True
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Sync dirty components
|
|
233
|
+
if "symbol" in self._dirty_flags and component_collection:
|
|
234
|
+
for component in component_collection:
|
|
235
|
+
self.sync_component_to_data(component)
|
|
236
|
+
|
|
237
|
+
# Sync dirty wires
|
|
238
|
+
if "wire" in self._dirty_flags and wire_collection:
|
|
239
|
+
for wire in wire_collection:
|
|
240
|
+
self.sync_wire_to_data(wire)
|
|
241
|
+
|
|
242
|
+
# Clear processed dirty flags
|
|
243
|
+
self._dirty_flags.clear()
|
|
244
|
+
logger.info("Incremental synchronization completed")
|
|
245
|
+
|
|
246
|
+
finally:
|
|
247
|
+
self._sync_lock = False
|
|
248
|
+
|
|
249
|
+
def add_component_to_data(self, component: Component) -> None:
|
|
250
|
+
"""
|
|
251
|
+
Add a new component to S-expression data.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
component: Component to add
|
|
255
|
+
"""
|
|
256
|
+
symbol_data = self._create_symbol_data_from_component(component)
|
|
257
|
+
|
|
258
|
+
if "symbol" not in self._data:
|
|
259
|
+
self._data["symbol"] = []
|
|
260
|
+
|
|
261
|
+
self._data["symbol"].append(symbol_data)
|
|
262
|
+
self.mark_dirty("symbol", "add", {"uuid": component.uuid})
|
|
263
|
+
|
|
264
|
+
logger.debug(f"Added component to data: {component.reference}")
|
|
265
|
+
|
|
266
|
+
def remove_component_from_data(self, component_uuid: str) -> bool:
|
|
267
|
+
"""
|
|
268
|
+
Remove a component from S-expression data.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
component_uuid: UUID of component to remove
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
True if removed, False if not found
|
|
275
|
+
"""
|
|
276
|
+
symbols = self._data.get("symbol", [])
|
|
277
|
+
|
|
278
|
+
for i, symbol_data in enumerate(symbols):
|
|
279
|
+
if symbol_data.get("uuid") == component_uuid:
|
|
280
|
+
del symbols[i]
|
|
281
|
+
self.mark_dirty("symbol", "remove", {"uuid": component_uuid})
|
|
282
|
+
logger.debug(f"Removed component from data: {component_uuid}")
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
logger.warning(f"Component not found for removal: {component_uuid}")
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
def add_wire_to_data(self, wire: Wire) -> None:
|
|
289
|
+
"""
|
|
290
|
+
Add a new wire to S-expression data.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
wire: Wire to add
|
|
294
|
+
"""
|
|
295
|
+
wire_data = self._create_wire_data_from_object(wire)
|
|
296
|
+
|
|
297
|
+
if "wire" not in self._data:
|
|
298
|
+
self._data["wire"] = []
|
|
299
|
+
|
|
300
|
+
self._data["wire"].append(wire_data)
|
|
301
|
+
self.mark_dirty("wire", "add", {"uuid": wire.uuid})
|
|
302
|
+
|
|
303
|
+
logger.debug(f"Added wire to data: {wire.uuid}")
|
|
304
|
+
|
|
305
|
+
def remove_wire_from_data(self, wire_uuid: str) -> bool:
|
|
306
|
+
"""
|
|
307
|
+
Remove a wire from S-expression data.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
wire_uuid: UUID of wire to remove
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
True if removed, False if not found
|
|
314
|
+
"""
|
|
315
|
+
wires = self._data.get("wire", [])
|
|
316
|
+
|
|
317
|
+
for i, wire_data in enumerate(wires):
|
|
318
|
+
if wire_data.get("uuid") == wire_uuid:
|
|
319
|
+
del wires[i]
|
|
320
|
+
self.mark_dirty("wire", "remove", {"uuid": wire_uuid})
|
|
321
|
+
logger.debug(f"Removed wire from data: {wire_uuid}")
|
|
322
|
+
return True
|
|
323
|
+
|
|
324
|
+
logger.warning(f"Wire not found for removal: {wire_uuid}")
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
def is_dirty(self, section: Optional[str] = None) -> bool:
|
|
328
|
+
"""
|
|
329
|
+
Check if data sections are dirty.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
section: Specific section to check, or None for any
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
True if section(s) are dirty
|
|
336
|
+
"""
|
|
337
|
+
if section:
|
|
338
|
+
return section in self._dirty_flags
|
|
339
|
+
return bool(self._dirty_flags)
|
|
340
|
+
|
|
341
|
+
def get_dirty_sections(self) -> Set[str]:
|
|
342
|
+
"""
|
|
343
|
+
Get all dirty data sections.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Set of dirty section names
|
|
347
|
+
"""
|
|
348
|
+
return self._dirty_flags.copy()
|
|
349
|
+
|
|
350
|
+
def clear_dirty_flags(self) -> None:
|
|
351
|
+
"""Clear all dirty flags."""
|
|
352
|
+
self._dirty_flags.clear()
|
|
353
|
+
logger.debug("Cleared all dirty flags")
|
|
354
|
+
|
|
355
|
+
def get_change_log(self) -> List[Dict[str, Any]]:
|
|
356
|
+
"""
|
|
357
|
+
Get the change log.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
List of change entries
|
|
361
|
+
"""
|
|
362
|
+
return self._change_log.copy()
|
|
363
|
+
|
|
364
|
+
def clear_change_log(self) -> None:
|
|
365
|
+
"""Clear the change log."""
|
|
366
|
+
self._change_log.clear()
|
|
367
|
+
logger.debug("Cleared change log")
|
|
368
|
+
|
|
369
|
+
def _update_symbol_data_from_component(
|
|
370
|
+
self, symbol_data: Dict[str, Any], component: Component
|
|
371
|
+
) -> None:
|
|
372
|
+
"""Update symbol S-expression data from component object."""
|
|
373
|
+
# Update position and rotation
|
|
374
|
+
symbol_data["at"] = [component.position.x, component.position.y, component.rotation]
|
|
375
|
+
|
|
376
|
+
# Update lib_id
|
|
377
|
+
symbol_data["lib_id"] = component.lib_id
|
|
378
|
+
|
|
379
|
+
# Update properties
|
|
380
|
+
if "property" not in symbol_data:
|
|
381
|
+
symbol_data["property"] = []
|
|
382
|
+
|
|
383
|
+
properties = symbol_data["property"]
|
|
384
|
+
|
|
385
|
+
# Update existing properties and add new ones
|
|
386
|
+
property_names = {prop.get("name") for prop in properties}
|
|
387
|
+
|
|
388
|
+
for name, value in component.properties.items():
|
|
389
|
+
# Find existing property or create new one
|
|
390
|
+
existing_prop = None
|
|
391
|
+
for prop in properties:
|
|
392
|
+
if prop.get("name") == name:
|
|
393
|
+
existing_prop = prop
|
|
394
|
+
break
|
|
395
|
+
|
|
396
|
+
if existing_prop:
|
|
397
|
+
existing_prop["value"] = value
|
|
398
|
+
else:
|
|
399
|
+
new_prop = {
|
|
400
|
+
"name": name,
|
|
401
|
+
"value": value,
|
|
402
|
+
"at": [0, 0, 0], # Default position
|
|
403
|
+
"effects": {"font": {"size": [1.27, 1.27]}},
|
|
404
|
+
}
|
|
405
|
+
properties.append(new_prop)
|
|
406
|
+
|
|
407
|
+
def _update_wire_data_from_object(self, wire_data: Dict[str, Any], wire: Wire) -> None:
|
|
408
|
+
"""Update wire S-expression data from wire object."""
|
|
409
|
+
# Update endpoints
|
|
410
|
+
wire_data["pts"] = [{"xy": [wire.start.x, wire.start.y]}, {"xy": [wire.end.x, wire.end.y]}]
|
|
411
|
+
|
|
412
|
+
# Update stroke
|
|
413
|
+
if "stroke" not in wire_data:
|
|
414
|
+
wire_data["stroke"] = {}
|
|
415
|
+
|
|
416
|
+
wire_data["stroke"]["width"] = wire.stroke_width
|
|
417
|
+
|
|
418
|
+
def _create_symbol_data_from_component(self, component: Component) -> Dict[str, Any]:
|
|
419
|
+
"""Create S-expression data structure from component object."""
|
|
420
|
+
symbol_data = {
|
|
421
|
+
"lib_id": component.lib_id,
|
|
422
|
+
"at": [component.position.x, component.position.y, component.rotation],
|
|
423
|
+
"uuid": component.uuid,
|
|
424
|
+
"property": [],
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# Add properties
|
|
428
|
+
for name, value in component.properties.items():
|
|
429
|
+
prop_data = {
|
|
430
|
+
"name": name,
|
|
431
|
+
"value": value,
|
|
432
|
+
"at": [0, 0, 0], # Default position relative to symbol
|
|
433
|
+
"effects": {"font": {"size": [1.27, 1.27]}},
|
|
434
|
+
}
|
|
435
|
+
symbol_data["property"].append(prop_data)
|
|
436
|
+
|
|
437
|
+
return symbol_data
|
|
438
|
+
|
|
439
|
+
def _create_wire_data_from_object(self, wire: Wire) -> Dict[str, Any]:
|
|
440
|
+
"""Create S-expression data structure from wire object."""
|
|
441
|
+
wire_data = {
|
|
442
|
+
"pts": [{"xy": [wire.start.x, wire.start.y]}, {"xy": [wire.end.x, wire.end.y]}],
|
|
443
|
+
"stroke": {"width": wire.stroke_width, "type": "default"},
|
|
444
|
+
"uuid": wire.uuid,
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return wire_data
|
|
448
|
+
|
|
449
|
+
def validate_data_consistency(
|
|
450
|
+
self, component_collection=None, wire_collection=None
|
|
451
|
+
) -> List[str]:
|
|
452
|
+
"""
|
|
453
|
+
Validate consistency between objects and S-expression data.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
component_collection: Collection of components to validate
|
|
457
|
+
wire_collection: Collection of wires to validate
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
List of consistency issues found
|
|
461
|
+
"""
|
|
462
|
+
issues = []
|
|
463
|
+
|
|
464
|
+
# Validate components
|
|
465
|
+
if component_collection:
|
|
466
|
+
symbols = self._data.get("symbol", [])
|
|
467
|
+
symbol_uuids = {sym.get("uuid") for sym in symbols if sym.get("uuid")}
|
|
468
|
+
component_uuids = {comp.uuid for comp in component_collection}
|
|
469
|
+
|
|
470
|
+
# Check for missing symbols
|
|
471
|
+
missing_symbols = component_uuids - symbol_uuids
|
|
472
|
+
for uuid in missing_symbols:
|
|
473
|
+
issues.append(f"Component {uuid} missing from symbol data")
|
|
474
|
+
|
|
475
|
+
# Check for orphaned symbols
|
|
476
|
+
orphaned_symbols = symbol_uuids - component_uuids
|
|
477
|
+
for uuid in orphaned_symbols:
|
|
478
|
+
issues.append(f"Symbol {uuid} has no corresponding component object")
|
|
479
|
+
|
|
480
|
+
# Validate wires
|
|
481
|
+
if wire_collection:
|
|
482
|
+
wires = self._data.get("wire", [])
|
|
483
|
+
wire_data_uuids = {w.get("uuid") for w in wires if w.get("uuid")}
|
|
484
|
+
wire_object_uuids = {wire.uuid for wire in wire_collection}
|
|
485
|
+
|
|
486
|
+
# Check for missing wire data
|
|
487
|
+
missing_wire_data = wire_object_uuids - wire_data_uuids
|
|
488
|
+
for uuid in missing_wire_data:
|
|
489
|
+
issues.append(f"Wire {uuid} missing from wire data")
|
|
490
|
+
|
|
491
|
+
# Check for orphaned wire data
|
|
492
|
+
orphaned_wire_data = wire_data_uuids - wire_object_uuids
|
|
493
|
+
for uuid in orphaned_wire_data:
|
|
494
|
+
issues.append(f"Wire data {uuid} has no corresponding wire object")
|
|
495
|
+
|
|
496
|
+
if issues:
|
|
497
|
+
logger.warning(f"Found {len(issues)} data consistency issues")
|
|
498
|
+
else:
|
|
499
|
+
logger.debug("Data consistency validation passed")
|
|
500
|
+
|
|
501
|
+
return issues
|