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,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parser registry for managing S-expression element parsers.
|
|
3
|
+
|
|
4
|
+
Provides a central registry for all element parsers and handles
|
|
5
|
+
dispatching parsing requests to the appropriate parser.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from ..interfaces.parser import IElementParser
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ElementParserRegistry:
|
|
17
|
+
"""
|
|
18
|
+
Central registry for all S-expression element parsers.
|
|
19
|
+
|
|
20
|
+
This class manages the registration of element-specific parsers
|
|
21
|
+
and provides a unified interface for parsing any S-expression element.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize the parser registry."""
|
|
26
|
+
self._parsers: Dict[str, IElementParser] = {}
|
|
27
|
+
self._fallback_parser: Optional[IElementParser] = None
|
|
28
|
+
self._logger = logger.getChild(self.__class__.__name__)
|
|
29
|
+
|
|
30
|
+
def register(self, element_type: str, parser: IElementParser) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Register a parser for a specific element type.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
element_type: The S-expression element type (e.g., "symbol", "wire")
|
|
36
|
+
parser: Parser instance that handles this element type
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: If element_type is already registered
|
|
40
|
+
"""
|
|
41
|
+
if element_type in self._parsers:
|
|
42
|
+
self._logger.warning(f"Overriding existing parser for element type: {element_type}")
|
|
43
|
+
|
|
44
|
+
self._parsers[element_type] = parser
|
|
45
|
+
self._logger.debug(f"Registered parser for element type: {element_type}")
|
|
46
|
+
|
|
47
|
+
def unregister(self, element_type: str) -> bool:
|
|
48
|
+
"""
|
|
49
|
+
Unregister a parser for a specific element type.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
element_type: The element type to unregister
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if parser was removed, False if not found
|
|
56
|
+
"""
|
|
57
|
+
if element_type in self._parsers:
|
|
58
|
+
del self._parsers[element_type]
|
|
59
|
+
self._logger.debug(f"Unregistered parser for element type: {element_type}")
|
|
60
|
+
return True
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
def set_fallback_parser(self, parser: IElementParser) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Set a fallback parser for unknown element types.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
parser: Parser to use when no specific parser is registered
|
|
69
|
+
"""
|
|
70
|
+
self._fallback_parser = parser
|
|
71
|
+
self._logger.debug("Set fallback parser")
|
|
72
|
+
|
|
73
|
+
def parse_element(self, element: List[Any]) -> Optional[Dict[str, Any]]:
|
|
74
|
+
"""
|
|
75
|
+
Parse an S-expression element using the appropriate registered parser.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
element: S-expression element to parse
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Parsed element data or None if parsing failed
|
|
82
|
+
"""
|
|
83
|
+
if not element or not isinstance(element, list):
|
|
84
|
+
self._logger.debug("Invalid element: not a list or empty")
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
element_type = element[0] if element else None
|
|
88
|
+
# Convert sexpdata.Symbol to string for lookup
|
|
89
|
+
element_type_str = str(element_type) if element_type else None
|
|
90
|
+
if not element_type_str:
|
|
91
|
+
self._logger.debug(f"Invalid element type: {element_type}")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Try specific parser first
|
|
95
|
+
parser = self._parsers.get(element_type_str)
|
|
96
|
+
if parser:
|
|
97
|
+
self._logger.debug(f"Using registered parser for element type: {element_type_str}")
|
|
98
|
+
return parser.parse(element)
|
|
99
|
+
|
|
100
|
+
# Try fallback parser
|
|
101
|
+
if self._fallback_parser:
|
|
102
|
+
self._logger.debug(f"Using fallback parser for unknown element type: {element_type_str}")
|
|
103
|
+
return self._fallback_parser.parse(element)
|
|
104
|
+
|
|
105
|
+
# No parser available
|
|
106
|
+
self._logger.warning(f"No parser available for element type: {element_type_str}")
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
def parse_elements(self, elements: List[List[Any]]) -> List[Dict[str, Any]]:
|
|
110
|
+
"""
|
|
111
|
+
Parse multiple S-expression elements.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
elements: List of S-expression elements to parse
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of parsed element data (excluding failed parses)
|
|
118
|
+
"""
|
|
119
|
+
results = []
|
|
120
|
+
for element in elements:
|
|
121
|
+
parsed = self.parse_element(element)
|
|
122
|
+
if parsed is not None:
|
|
123
|
+
results.append(parsed)
|
|
124
|
+
|
|
125
|
+
self._logger.debug(f"Parsed {len(results)} of {len(elements)} elements")
|
|
126
|
+
return results
|
|
127
|
+
|
|
128
|
+
def get_registered_types(self) -> List[str]:
|
|
129
|
+
"""
|
|
130
|
+
Get list of all registered element types.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List of registered element type names
|
|
134
|
+
"""
|
|
135
|
+
return list(self._parsers.keys())
|
|
136
|
+
|
|
137
|
+
def has_parser(self, element_type: str) -> bool:
|
|
138
|
+
"""
|
|
139
|
+
Check if a parser is registered for the given element type.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
element_type: Element type to check
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if parser is registered
|
|
146
|
+
"""
|
|
147
|
+
return element_type in self._parsers
|
|
148
|
+
|
|
149
|
+
def clear(self) -> None:
|
|
150
|
+
"""Clear all registered parsers."""
|
|
151
|
+
self._parsers.clear()
|
|
152
|
+
self._fallback_parser = None
|
|
153
|
+
self._logger.debug("Cleared all registered parsers")
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Symbol element parser for S-expression symbol definitions.
|
|
3
|
+
|
|
4
|
+
Handles parsing of symbol elements with properties, positions, and library references.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import sexpdata
|
|
11
|
+
|
|
12
|
+
from .base import BaseElementParser
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SymbolParser(BaseElementParser):
|
|
18
|
+
"""Parser for symbol S-expression elements."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize symbol parser."""
|
|
22
|
+
super().__init__("symbol")
|
|
23
|
+
|
|
24
|
+
def parse_element(self, element: List[Any]) -> Optional[Dict[str, Any]]:
|
|
25
|
+
"""
|
|
26
|
+
Parse a symbol S-expression element.
|
|
27
|
+
|
|
28
|
+
Expected format:
|
|
29
|
+
(symbol (lib_id "Device:R") (at x y angle) (property "Reference" "R1" ...)...)
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: Symbol S-expression element
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Parsed symbol data with library ID, position, and properties
|
|
36
|
+
"""
|
|
37
|
+
symbol_data = {
|
|
38
|
+
"lib_id": "",
|
|
39
|
+
"position": {"x": 0, "y": 0, "angle": 0},
|
|
40
|
+
"mirror": "",
|
|
41
|
+
"unit": 1,
|
|
42
|
+
"in_bom": True,
|
|
43
|
+
"on_board": True,
|
|
44
|
+
"fields_autoplaced": False,
|
|
45
|
+
"uuid": None,
|
|
46
|
+
"properties": [],
|
|
47
|
+
"instances": []
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for elem in element[1:]:
|
|
51
|
+
if not isinstance(elem, list):
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
55
|
+
|
|
56
|
+
if elem_type == "lib_id":
|
|
57
|
+
symbol_data["lib_id"] = str(elem[1]) if len(elem) > 1 else ""
|
|
58
|
+
elif elem_type == "at":
|
|
59
|
+
self._parse_position(elem, symbol_data)
|
|
60
|
+
elif elem_type == "mirror":
|
|
61
|
+
symbol_data["mirror"] = str(elem[1]) if len(elem) > 1 else ""
|
|
62
|
+
elif elem_type == "unit":
|
|
63
|
+
symbol_data["unit"] = int(elem[1]) if len(elem) > 1 else 1
|
|
64
|
+
elif elem_type == "in_bom":
|
|
65
|
+
symbol_data["in_bom"] = self._parse_boolean(elem[1]) if len(elem) > 1 else True
|
|
66
|
+
elif elem_type == "on_board":
|
|
67
|
+
symbol_data["on_board"] = self._parse_boolean(elem[1]) if len(elem) > 1 else True
|
|
68
|
+
elif elem_type == "fields_autoplaced":
|
|
69
|
+
symbol_data["fields_autoplaced"] = True
|
|
70
|
+
elif elem_type == "uuid":
|
|
71
|
+
symbol_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
72
|
+
elif elem_type == "property":
|
|
73
|
+
prop = self._parse_property(elem)
|
|
74
|
+
if prop:
|
|
75
|
+
symbol_data["properties"].append(prop)
|
|
76
|
+
elif elem_type == "instances":
|
|
77
|
+
symbol_data["instances"] = self._parse_instances(elem)
|
|
78
|
+
|
|
79
|
+
return symbol_data
|
|
80
|
+
|
|
81
|
+
def _parse_position(self, at_element: List[Any], symbol_data: Dict[str, Any]) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Parse position from at element.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
at_element: (at x y [angle])
|
|
87
|
+
symbol_data: Symbol data dictionary to update
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
if len(at_element) >= 3:
|
|
91
|
+
symbol_data["position"]["x"] = float(at_element[1])
|
|
92
|
+
symbol_data["position"]["y"] = float(at_element[2])
|
|
93
|
+
if len(at_element) >= 4:
|
|
94
|
+
symbol_data["position"]["angle"] = float(at_element[3])
|
|
95
|
+
except (ValueError, IndexError) as e:
|
|
96
|
+
self._logger.warning(f"Invalid position coordinates: {at_element}, error: {e}")
|
|
97
|
+
|
|
98
|
+
def _parse_property(self, prop_element: List[Any]) -> Optional[Dict[str, Any]]:
|
|
99
|
+
"""
|
|
100
|
+
Parse a property element.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
prop_element: (property "name" "value" (at x y angle) ...)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Parsed property data or None if invalid
|
|
107
|
+
"""
|
|
108
|
+
if len(prop_element) < 3:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
prop_data = {
|
|
112
|
+
"name": str(prop_element[1]),
|
|
113
|
+
"value": str(prop_element[2]),
|
|
114
|
+
"position": {"x": 0, "y": 0, "angle": 0},
|
|
115
|
+
"effects": {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Parse additional property elements
|
|
119
|
+
for elem in prop_element[3:]:
|
|
120
|
+
if isinstance(elem, list) and len(elem) > 0:
|
|
121
|
+
elem_type = str(elem[0])
|
|
122
|
+
if elem_type == "at":
|
|
123
|
+
self._parse_property_position(elem, prop_data)
|
|
124
|
+
elif elem_type == "effects":
|
|
125
|
+
prop_data["effects"] = self._parse_effects(elem)
|
|
126
|
+
|
|
127
|
+
return prop_data
|
|
128
|
+
|
|
129
|
+
def _parse_property_position(self, at_element: List[Any], prop_data: Dict[str, Any]) -> None:
|
|
130
|
+
"""Parse property position from at element."""
|
|
131
|
+
try:
|
|
132
|
+
if len(at_element) >= 3:
|
|
133
|
+
prop_data["position"]["x"] = float(at_element[1])
|
|
134
|
+
prop_data["position"]["y"] = float(at_element[2])
|
|
135
|
+
if len(at_element) >= 4:
|
|
136
|
+
prop_data["position"]["angle"] = float(at_element[3])
|
|
137
|
+
except (ValueError, IndexError) as e:
|
|
138
|
+
self._logger.warning(f"Invalid property position: {at_element}, error: {e}")
|
|
139
|
+
|
|
140
|
+
def _parse_effects(self, effects_element: List[Any]) -> Dict[str, Any]:
|
|
141
|
+
"""Parse effects element for text formatting."""
|
|
142
|
+
effects = {
|
|
143
|
+
"font_size": 1.27,
|
|
144
|
+
"font_thickness": 0.15,
|
|
145
|
+
"bold": False,
|
|
146
|
+
"italic": False,
|
|
147
|
+
"hide": False,
|
|
148
|
+
"justify": []
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for elem in effects_element[1:]:
|
|
152
|
+
if isinstance(elem, list) and len(elem) > 0:
|
|
153
|
+
elem_type = str(elem[0])
|
|
154
|
+
if elem_type == "font":
|
|
155
|
+
self._parse_font(elem, effects)
|
|
156
|
+
elif elem_type == "justify":
|
|
157
|
+
effects["justify"] = [str(j) for j in elem[1:]]
|
|
158
|
+
elif elem_type == "hide":
|
|
159
|
+
effects["hide"] = True
|
|
160
|
+
|
|
161
|
+
return effects
|
|
162
|
+
|
|
163
|
+
def _parse_font(self, font_element: List[Any], effects: Dict[str, Any]) -> None:
|
|
164
|
+
"""Parse font element within effects."""
|
|
165
|
+
for elem in font_element[1:]:
|
|
166
|
+
if isinstance(elem, list) and len(elem) > 0:
|
|
167
|
+
elem_type = str(elem[0])
|
|
168
|
+
if elem_type == "size":
|
|
169
|
+
try:
|
|
170
|
+
if len(elem) >= 3:
|
|
171
|
+
effects["font_size"] = float(elem[1])
|
|
172
|
+
except (ValueError, IndexError):
|
|
173
|
+
pass
|
|
174
|
+
elif elem_type == "thickness":
|
|
175
|
+
try:
|
|
176
|
+
effects["font_thickness"] = float(elem[1])
|
|
177
|
+
except (ValueError, IndexError):
|
|
178
|
+
pass
|
|
179
|
+
elif elem_type == "bold":
|
|
180
|
+
effects["bold"] = True
|
|
181
|
+
elif elem_type == "italic":
|
|
182
|
+
effects["italic"] = True
|
|
183
|
+
|
|
184
|
+
def _parse_instances(self, instances_element: List[Any]) -> List[Dict[str, Any]]:
|
|
185
|
+
"""Parse instances element for symbol instances."""
|
|
186
|
+
instances = []
|
|
187
|
+
for elem in instances_element[1:]:
|
|
188
|
+
if isinstance(elem, list) and len(elem) > 0 and str(elem[0]) == "instance":
|
|
189
|
+
instance = self._parse_instance(elem)
|
|
190
|
+
if instance:
|
|
191
|
+
instances.append(instance)
|
|
192
|
+
return instances
|
|
193
|
+
|
|
194
|
+
def _parse_instance(self, instance_element: List[Any]) -> Optional[Dict[str, Any]]:
|
|
195
|
+
"""Parse a single instance element."""
|
|
196
|
+
instance = {
|
|
197
|
+
"project": "",
|
|
198
|
+
"path": "",
|
|
199
|
+
"reference": "",
|
|
200
|
+
"unit": 1
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for elem in instance_element[1:]:
|
|
204
|
+
if isinstance(elem, list) and len(elem) >= 2:
|
|
205
|
+
elem_type = str(elem[0])
|
|
206
|
+
if elem_type == "project":
|
|
207
|
+
instance["project"] = str(elem[1])
|
|
208
|
+
elif elem_type == "path":
|
|
209
|
+
instance["path"] = str(elem[1])
|
|
210
|
+
elif elem_type == "reference":
|
|
211
|
+
instance["reference"] = str(elem[1])
|
|
212
|
+
elif elem_type == "unit":
|
|
213
|
+
try:
|
|
214
|
+
instance["unit"] = int(elem[1])
|
|
215
|
+
except (ValueError, IndexError):
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
return instance if instance["reference"] else None
|
|
219
|
+
|
|
220
|
+
def _parse_boolean(self, value: Any) -> bool:
|
|
221
|
+
"""Parse boolean value from S-expression."""
|
|
222
|
+
if isinstance(value, str):
|
|
223
|
+
return value.lower() in ("yes", "true", "1")
|
|
224
|
+
elif isinstance(value, sexpdata.Symbol):
|
|
225
|
+
return str(value).lower() in ("yes", "true", "1")
|
|
226
|
+
else:
|
|
227
|
+
return bool(value)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wire element parser for S-expression wire definitions.
|
|
3
|
+
|
|
4
|
+
Handles parsing of wire elements with points, stroke properties, and UUIDs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import sexpdata
|
|
11
|
+
|
|
12
|
+
from .base import BaseElementParser
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WireParser(BaseElementParser):
|
|
18
|
+
"""Parser for wire S-expression elements."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize wire parser."""
|
|
22
|
+
super().__init__("wire")
|
|
23
|
+
|
|
24
|
+
def parse_element(self, element: List[Any]) -> Optional[Dict[str, Any]]:
|
|
25
|
+
"""
|
|
26
|
+
Parse a wire S-expression element.
|
|
27
|
+
|
|
28
|
+
Expected format:
|
|
29
|
+
(wire (pts (xy x1 y1) (xy x2 y2) ...) (stroke (width w) (type t)) (uuid "..."))
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: Wire S-expression element
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Parsed wire data with points, stroke, and UUID information
|
|
36
|
+
"""
|
|
37
|
+
wire_data = {
|
|
38
|
+
"points": [],
|
|
39
|
+
"stroke_width": 0.0,
|
|
40
|
+
"stroke_type": "default",
|
|
41
|
+
"uuid": None,
|
|
42
|
+
"wire_type": "wire" # Default to wire (vs bus)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for elem in element[1:]:
|
|
46
|
+
if not isinstance(elem, list):
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
50
|
+
|
|
51
|
+
if elem_type == "pts":
|
|
52
|
+
self._parse_points(elem, wire_data)
|
|
53
|
+
elif elem_type == "stroke":
|
|
54
|
+
self._parse_stroke(elem, wire_data)
|
|
55
|
+
elif elem_type == "uuid":
|
|
56
|
+
wire_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
57
|
+
|
|
58
|
+
# Validate wire has sufficient points
|
|
59
|
+
if len(wire_data["points"]) >= 2:
|
|
60
|
+
return wire_data
|
|
61
|
+
else:
|
|
62
|
+
self._logger.warning(f"Wire has insufficient points: {len(wire_data['points'])}")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def _parse_points(self, pts_element: List[Any], wire_data: Dict[str, Any]) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Parse points from pts element.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
pts_element: (pts (xy x1 y1) (xy x2 y2) ...)
|
|
71
|
+
wire_data: Wire data dictionary to update
|
|
72
|
+
"""
|
|
73
|
+
for pt in pts_element[1:]:
|
|
74
|
+
if isinstance(pt, list) and len(pt) >= 3:
|
|
75
|
+
if str(pt[0]) == "xy":
|
|
76
|
+
try:
|
|
77
|
+
x, y = float(pt[1]), float(pt[2])
|
|
78
|
+
wire_data["points"].append({"x": x, "y": y})
|
|
79
|
+
except (ValueError, IndexError) as e:
|
|
80
|
+
self._logger.warning(f"Invalid point coordinates: {pt}, error: {e}")
|
|
81
|
+
|
|
82
|
+
def _parse_stroke(self, stroke_element: List[Any], wire_data: Dict[str, Any]) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Parse stroke properties from stroke element.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
stroke_element: (stroke (width w) (type t))
|
|
88
|
+
wire_data: Wire data dictionary to update
|
|
89
|
+
"""
|
|
90
|
+
for stroke_elem in stroke_element[1:]:
|
|
91
|
+
if isinstance(stroke_elem, list) and len(stroke_elem) >= 2:
|
|
92
|
+
stroke_type = str(stroke_elem[0])
|
|
93
|
+
try:
|
|
94
|
+
if stroke_type == "width":
|
|
95
|
+
wire_data["stroke_width"] = float(stroke_elem[1])
|
|
96
|
+
elif stroke_type == "type":
|
|
97
|
+
wire_data["stroke_type"] = str(stroke_elem[1])
|
|
98
|
+
except (ValueError, IndexError) as e:
|
|
99
|
+
self._logger.warning(f"Invalid stroke property: {stroke_elem}, error: {e}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Symbol resolution and caching architecture for KiCAD schematic API.
|
|
3
|
+
|
|
4
|
+
This module provides a unified symbol resolution system that separates concerns
|
|
5
|
+
between caching, inheritance resolution, and validation while maintaining
|
|
6
|
+
high performance and exact format preservation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .cache import ISymbolCache, SymbolCache
|
|
10
|
+
from .resolver import SymbolResolver
|
|
11
|
+
from .validators import SymbolValidator
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ISymbolCache",
|
|
15
|
+
"SymbolCache",
|
|
16
|
+
"SymbolResolver",
|
|
17
|
+
"SymbolValidator",
|
|
18
|
+
]
|