kicad-sch-api 0.3.0__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.
Files changed (112) hide show
  1. kicad_sch_api/__init__.py +68 -3
  2. kicad_sch_api/cli/__init__.py +45 -0
  3. kicad_sch_api/cli/base.py +302 -0
  4. kicad_sch_api/cli/bom.py +164 -0
  5. kicad_sch_api/cli/erc.py +229 -0
  6. kicad_sch_api/cli/export_docs.py +289 -0
  7. kicad_sch_api/cli/kicad_to_python.py +169 -0
  8. kicad_sch_api/cli/netlist.py +94 -0
  9. kicad_sch_api/cli/types.py +43 -0
  10. kicad_sch_api/collections/__init__.py +36 -0
  11. kicad_sch_api/collections/base.py +604 -0
  12. kicad_sch_api/collections/components.py +1623 -0
  13. kicad_sch_api/collections/junctions.py +206 -0
  14. kicad_sch_api/collections/labels.py +508 -0
  15. kicad_sch_api/collections/wires.py +292 -0
  16. kicad_sch_api/core/__init__.py +37 -2
  17. kicad_sch_api/core/collections/__init__.py +5 -0
  18. kicad_sch_api/core/collections/base.py +248 -0
  19. kicad_sch_api/core/component_bounds.py +34 -7
  20. kicad_sch_api/core/components.py +213 -52
  21. kicad_sch_api/core/config.py +110 -15
  22. kicad_sch_api/core/connectivity.py +692 -0
  23. kicad_sch_api/core/exceptions.py +175 -0
  24. kicad_sch_api/core/factories/__init__.py +5 -0
  25. kicad_sch_api/core/factories/element_factory.py +278 -0
  26. kicad_sch_api/core/formatter.py +60 -9
  27. kicad_sch_api/core/geometry.py +94 -5
  28. kicad_sch_api/core/junctions.py +26 -75
  29. kicad_sch_api/core/labels.py +324 -0
  30. kicad_sch_api/core/managers/__init__.py +30 -0
  31. kicad_sch_api/core/managers/base.py +76 -0
  32. kicad_sch_api/core/managers/file_io.py +246 -0
  33. kicad_sch_api/core/managers/format_sync.py +502 -0
  34. kicad_sch_api/core/managers/graphics.py +580 -0
  35. kicad_sch_api/core/managers/hierarchy.py +661 -0
  36. kicad_sch_api/core/managers/metadata.py +271 -0
  37. kicad_sch_api/core/managers/sheet.py +492 -0
  38. kicad_sch_api/core/managers/text_elements.py +537 -0
  39. kicad_sch_api/core/managers/validation.py +476 -0
  40. kicad_sch_api/core/managers/wire.py +410 -0
  41. kicad_sch_api/core/nets.py +305 -0
  42. kicad_sch_api/core/no_connects.py +252 -0
  43. kicad_sch_api/core/parser.py +194 -970
  44. kicad_sch_api/core/parsing_utils.py +63 -0
  45. kicad_sch_api/core/pin_utils.py +103 -9
  46. kicad_sch_api/core/schematic.py +1328 -1079
  47. kicad_sch_api/core/texts.py +316 -0
  48. kicad_sch_api/core/types.py +159 -23
  49. kicad_sch_api/core/wires.py +27 -75
  50. kicad_sch_api/exporters/__init__.py +10 -0
  51. kicad_sch_api/exporters/python_generator.py +610 -0
  52. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  53. kicad_sch_api/geometry/__init__.py +38 -0
  54. kicad_sch_api/geometry/font_metrics.py +22 -0
  55. kicad_sch_api/geometry/routing.py +211 -0
  56. kicad_sch_api/geometry/symbol_bbox.py +608 -0
  57. kicad_sch_api/interfaces/__init__.py +17 -0
  58. kicad_sch_api/interfaces/parser.py +76 -0
  59. kicad_sch_api/interfaces/repository.py +70 -0
  60. kicad_sch_api/interfaces/resolver.py +117 -0
  61. kicad_sch_api/parsers/__init__.py +14 -0
  62. kicad_sch_api/parsers/base.py +145 -0
  63. kicad_sch_api/parsers/elements/__init__.py +22 -0
  64. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  65. kicad_sch_api/parsers/elements/label_parser.py +216 -0
  66. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  67. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  68. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  69. kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
  70. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  71. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  72. kicad_sch_api/parsers/registry.py +155 -0
  73. kicad_sch_api/parsers/utils.py +80 -0
  74. kicad_sch_api/symbols/__init__.py +18 -0
  75. kicad_sch_api/symbols/cache.py +467 -0
  76. kicad_sch_api/symbols/resolver.py +361 -0
  77. kicad_sch_api/symbols/validators.py +504 -0
  78. kicad_sch_api/utils/logging.py +555 -0
  79. kicad_sch_api/utils/logging_decorators.py +587 -0
  80. kicad_sch_api/utils/validation.py +16 -22
  81. kicad_sch_api/validation/__init__.py +25 -0
  82. kicad_sch_api/validation/erc.py +171 -0
  83. kicad_sch_api/validation/erc_models.py +203 -0
  84. kicad_sch_api/validation/pin_matrix.py +243 -0
  85. kicad_sch_api/validation/validators.py +391 -0
  86. kicad_sch_api/wrappers/__init__.py +14 -0
  87. kicad_sch_api/wrappers/base.py +89 -0
  88. kicad_sch_api/wrappers/wire.py +198 -0
  89. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  90. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  91. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  92. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  93. mcp_server/__init__.py +34 -0
  94. mcp_server/example_logging_integration.py +506 -0
  95. mcp_server/models.py +252 -0
  96. mcp_server/server.py +357 -0
  97. mcp_server/tools/__init__.py +32 -0
  98. mcp_server/tools/component_tools.py +516 -0
  99. mcp_server/tools/connectivity_tools.py +532 -0
  100. mcp_server/tools/consolidated_tools.py +1216 -0
  101. mcp_server/tools/pin_discovery.py +333 -0
  102. mcp_server/utils/__init__.py +38 -0
  103. mcp_server/utils/logging.py +127 -0
  104. mcp_server/utils.py +36 -0
  105. kicad_sch_api/core/manhattan_routing.py +0 -430
  106. kicad_sch_api/core/simple_manhattan.py +0 -228
  107. kicad_sch_api/core/wire_routing.py +0 -380
  108. kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
  109. kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
  110. kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
  111. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  112. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,352 @@
1
+ """
2
+ Hierarchical sheet elements parser for KiCAD schematics.
3
+
4
+ Handles parsing and serialization of Hierarchical sheet elements.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ import sexpdata
11
+
12
+ from ...core.config import config
13
+ from ..base import BaseElementParser
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class SheetParser(BaseElementParser):
19
+ """Parser for Hierarchical sheet elements."""
20
+
21
+ def __init__(self):
22
+ """Initialize sheet parser."""
23
+ super().__init__("sheet")
24
+
25
+ def _parse_sheet(self, item: List[Any]) -> Optional[Dict[str, Any]]:
26
+ """Parse a hierarchical sheet."""
27
+ # Complex format with position, size, properties, pins, instances
28
+ sheet_data = {
29
+ "position": {"x": 0, "y": 0},
30
+ "size": {"width": 0, "height": 0},
31
+ "exclude_from_sim": False,
32
+ "in_bom": True,
33
+ "on_board": True,
34
+ "dnp": False,
35
+ "fields_autoplaced": True,
36
+ "stroke_width": 0.1524,
37
+ "stroke_type": "solid",
38
+ "fill_color": (0, 0, 0, 0.0),
39
+ "uuid": None,
40
+ "name": "Sheet",
41
+ "filename": "sheet.kicad_sch",
42
+ "pins": [],
43
+ "project_name": "",
44
+ "page_number": "2",
45
+ }
46
+
47
+ for elem in item[1:]:
48
+ if not isinstance(elem, list):
49
+ continue
50
+
51
+ elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
52
+
53
+ if elem_type == "at":
54
+ if len(elem) >= 3:
55
+ sheet_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
56
+ elif elem_type == "size":
57
+ if len(elem) >= 3:
58
+ sheet_data["size"] = {"width": float(elem[1]), "height": float(elem[2])}
59
+ elif elem_type == "exclude_from_sim":
60
+ sheet_data["exclude_from_sim"] = str(elem[1]) == "yes" if len(elem) > 1 else False
61
+ elif elem_type == "in_bom":
62
+ sheet_data["in_bom"] = str(elem[1]) == "yes" if len(elem) > 1 else True
63
+ elif elem_type == "on_board":
64
+ sheet_data["on_board"] = str(elem[1]) == "yes" if len(elem) > 1 else True
65
+ elif elem_type == "dnp":
66
+ sheet_data["dnp"] = str(elem[1]) == "yes" if len(elem) > 1 else False
67
+ elif elem_type == "fields_autoplaced":
68
+ sheet_data["fields_autoplaced"] = str(elem[1]) == "yes" if len(elem) > 1 else True
69
+ elif elem_type == "stroke":
70
+ for stroke_elem in elem[1:]:
71
+ if isinstance(stroke_elem, list):
72
+ stroke_type = str(stroke_elem[0])
73
+ if stroke_type == "width" and len(stroke_elem) >= 2:
74
+ sheet_data["stroke_width"] = float(stroke_elem[1])
75
+ elif stroke_type == "type" and len(stroke_elem) >= 2:
76
+ sheet_data["stroke_type"] = str(stroke_elem[1])
77
+ elif elem_type == "fill":
78
+ for fill_elem in elem[1:]:
79
+ if isinstance(fill_elem, list) and str(fill_elem[0]) == "color":
80
+ if len(fill_elem) >= 5:
81
+ sheet_data["fill_color"] = (
82
+ int(fill_elem[1]),
83
+ int(fill_elem[2]),
84
+ int(fill_elem[3]),
85
+ float(fill_elem[4]),
86
+ )
87
+ elif elem_type == "uuid":
88
+ sheet_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
89
+ elif elem_type == "property":
90
+ if len(elem) >= 3:
91
+ prop_name = str(elem[1])
92
+ prop_value = str(elem[2])
93
+ if prop_name == "Sheetname":
94
+ sheet_data["name"] = prop_value
95
+ elif prop_name == "Sheetfile":
96
+ sheet_data["filename"] = prop_value
97
+ elif elem_type == "pin":
98
+ # Parse sheet pin - reuse existing _parse_sheet_pin helper
99
+ pin_data = self._parse_sheet_pin_for_read(elem)
100
+ if pin_data:
101
+ sheet_data["pins"].append(pin_data)
102
+ elif elem_type == "instances":
103
+ # Parse instances for project name and page number
104
+ for inst_elem in elem[1:]:
105
+ if isinstance(inst_elem, list) and str(inst_elem[0]) == "project":
106
+ if len(inst_elem) >= 2:
107
+ sheet_data["project_name"] = str(inst_elem[1])
108
+ for path_elem in inst_elem[2:]:
109
+ if isinstance(path_elem, list) and str(path_elem[0]) == "path":
110
+ for page_elem in path_elem[1:]:
111
+ if isinstance(page_elem, list) and str(page_elem[0]) == "page":
112
+ sheet_data["page_number"] = (
113
+ str(page_elem[1]) if len(page_elem) > 1 else "2"
114
+ )
115
+
116
+ return sheet_data
117
+
118
+
119
+ def _parse_sheet_pin_for_read(self, item: List[Any]) -> Optional[Dict[str, Any]]:
120
+ """Parse a sheet pin (for reading during sheet parsing)."""
121
+ # Format: (pin "name" type (at x y rotation) (uuid ...) (effects ...))
122
+ if len(item) < 3:
123
+ return None
124
+
125
+ pin_data = {
126
+ "name": str(item[1]),
127
+ "pin_type": str(item[2]) if len(item) > 2 else "input",
128
+ "position": {"x": 0, "y": 0},
129
+ "rotation": 0,
130
+ "size": config.defaults.font_size,
131
+ "justify": "right",
132
+ "uuid": None,
133
+ }
134
+
135
+ for elem in item[3:]:
136
+ if not isinstance(elem, list):
137
+ continue
138
+
139
+ elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
140
+
141
+ if elem_type == "at":
142
+ if len(elem) >= 3:
143
+ pin_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
144
+ if len(elem) >= 4:
145
+ pin_data["rotation"] = float(elem[3])
146
+ elif elem_type == "uuid":
147
+ pin_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
148
+ elif elem_type == "effects":
149
+ for effect_elem in elem[1:]:
150
+ if isinstance(effect_elem, list):
151
+ effect_type = str(effect_elem[0])
152
+ if effect_type == "font":
153
+ for font_elem in effect_elem[1:]:
154
+ if isinstance(font_elem, list) and str(font_elem[0]) == "size":
155
+ if len(font_elem) >= 2:
156
+ pin_data["size"] = float(font_elem[1])
157
+ elif effect_type == "justify":
158
+ if len(effect_elem) >= 2:
159
+ pin_data["justify"] = str(effect_elem[1])
160
+
161
+ return pin_data
162
+
163
+
164
+ def _parse_sheet_instances(self, item: List[Any]) -> List[Dict[str, Any]]:
165
+ """Parse sheet_instances section."""
166
+ sheet_instances = []
167
+ for sheet_item in item[1:]: # Skip 'sheet_instances' header
168
+ if isinstance(sheet_item, list) and len(sheet_item) > 0:
169
+ sheet_data = {"path": "/", "page": "1"}
170
+ for element in sheet_item[1:]: # Skip element header
171
+ if isinstance(element, list) and len(element) >= 2:
172
+ key = (
173
+ str(element[0])
174
+ if isinstance(element[0], sexpdata.Symbol)
175
+ else str(element[0])
176
+ )
177
+ if key == "path":
178
+ sheet_data["path"] = element[1]
179
+ elif key == "page":
180
+ sheet_data["page"] = element[1]
181
+ sheet_instances.append(sheet_data)
182
+ return sheet_instances
183
+
184
+
185
+ def _sheet_to_sexp(self, sheet_data: Dict[str, Any], schematic_uuid: str) -> List[Any]:
186
+ """Convert hierarchical sheet to S-expression."""
187
+ sexp = [sexpdata.Symbol("sheet")]
188
+
189
+ # Add position
190
+ pos = sheet_data["position"]
191
+ x, y = pos["x"], pos["y"]
192
+ if isinstance(x, float) and x.is_integer():
193
+ x = int(x)
194
+ if isinstance(y, float) and y.is_integer():
195
+ y = int(y)
196
+ sexp.append([sexpdata.Symbol("at"), x, y])
197
+
198
+ # Add size
199
+ size = sheet_data["size"]
200
+ w, h = size["width"], size["height"]
201
+ sexp.append([sexpdata.Symbol("size"), w, h])
202
+
203
+ # Add basic properties
204
+ sexp.append(
205
+ [
206
+ sexpdata.Symbol("exclude_from_sim"),
207
+ sexpdata.Symbol("yes" if sheet_data.get("exclude_from_sim", False) else "no"),
208
+ ]
209
+ )
210
+ sexp.append(
211
+ [
212
+ sexpdata.Symbol("in_bom"),
213
+ sexpdata.Symbol("yes" if sheet_data.get("in_bom", True) else "no"),
214
+ ]
215
+ )
216
+ sexp.append(
217
+ [
218
+ sexpdata.Symbol("on_board"),
219
+ sexpdata.Symbol("yes" if sheet_data.get("on_board", True) else "no"),
220
+ ]
221
+ )
222
+ sexp.append(
223
+ [
224
+ sexpdata.Symbol("dnp"),
225
+ sexpdata.Symbol("yes" if sheet_data.get("dnp", False) else "no"),
226
+ ]
227
+ )
228
+ sexp.append(
229
+ [
230
+ sexpdata.Symbol("fields_autoplaced"),
231
+ sexpdata.Symbol("yes" if sheet_data.get("fields_autoplaced", True) else "no"),
232
+ ]
233
+ )
234
+
235
+ # Add stroke
236
+ stroke_width = sheet_data.get("stroke_width", 0.1524)
237
+ stroke_type = sheet_data.get("stroke_type", "solid")
238
+ stroke_sexp = [sexpdata.Symbol("stroke")]
239
+ stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
240
+ stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
241
+ sexp.append(stroke_sexp)
242
+
243
+ # Add fill
244
+ fill_color = sheet_data.get("fill_color", (0, 0, 0, 0.0))
245
+ fill_sexp = [sexpdata.Symbol("fill")]
246
+ fill_sexp.append(
247
+ [sexpdata.Symbol("color"), fill_color[0], fill_color[1], fill_color[2], fill_color[3]]
248
+ )
249
+ sexp.append(fill_sexp)
250
+
251
+ # Add UUID
252
+ if "uuid" in sheet_data:
253
+ sexp.append([sexpdata.Symbol("uuid"), sheet_data["uuid"]])
254
+
255
+ # Add sheet properties (name and filename)
256
+ name = sheet_data.get("name", "Sheet")
257
+ filename = sheet_data.get("filename", "sheet.kicad_sch")
258
+
259
+ # Sheetname property
260
+ from ...core.config import config
261
+
262
+ name_prop = [sexpdata.Symbol("property"), "Sheetname", name]
263
+ name_prop.append(
264
+ [sexpdata.Symbol("at"), x, round(y + config.sheet.name_offset_y, 4), 0]
265
+ ) # Above sheet
266
+ name_prop.append(
267
+ [
268
+ sexpdata.Symbol("effects"),
269
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), config.defaults.font_size, config.defaults.font_size]],
270
+ [sexpdata.Symbol("justify"), sexpdata.Symbol("left"), sexpdata.Symbol("bottom")],
271
+ ]
272
+ )
273
+ sexp.append(name_prop)
274
+
275
+ # Sheetfile property
276
+ file_prop = [sexpdata.Symbol("property"), "Sheetfile", filename]
277
+ file_prop.append(
278
+ [sexpdata.Symbol("at"), x, round(y + h + config.sheet.file_offset_y, 4), 0]
279
+ ) # Below sheet
280
+ file_prop.append(
281
+ [
282
+ sexpdata.Symbol("effects"),
283
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), config.defaults.font_size, config.defaults.font_size]],
284
+ [sexpdata.Symbol("justify"), sexpdata.Symbol("left"), sexpdata.Symbol("top")],
285
+ ]
286
+ )
287
+ sexp.append(file_prop)
288
+
289
+ # Add sheet pins if any
290
+ for pin in sheet_data.get("pins", []):
291
+ pin_sexp = self._sheet_pin_to_sexp(pin)
292
+ sexp.append(pin_sexp)
293
+
294
+ # Add instances
295
+ if schematic_uuid:
296
+ instances_sexp = [sexpdata.Symbol("instances")]
297
+ project_name = sheet_data.get("project_name", "")
298
+ page_number = sheet_data.get("page_number", "2")
299
+ project_sexp = [sexpdata.Symbol("project"), project_name]
300
+ path_sexp = [sexpdata.Symbol("path"), f"/{schematic_uuid}"]
301
+ path_sexp.append([sexpdata.Symbol("page"), page_number])
302
+ project_sexp.append(path_sexp)
303
+ instances_sexp.append(project_sexp)
304
+ sexp.append(instances_sexp)
305
+
306
+ return sexp
307
+
308
+
309
+ def _sheet_pin_to_sexp(self, pin_data: Dict[str, Any]) -> List[Any]:
310
+ """Convert sheet pin to S-expression."""
311
+ pin_sexp = [
312
+ sexpdata.Symbol("pin"),
313
+ pin_data["name"],
314
+ sexpdata.Symbol(pin_data.get("pin_type", "input")),
315
+ ]
316
+
317
+ # Add position
318
+ pos = pin_data["position"]
319
+ x, y = pos["x"], pos["y"]
320
+ rotation = pin_data.get("rotation", 0)
321
+ pin_sexp.append([sexpdata.Symbol("at"), x, y, rotation])
322
+
323
+ # Add UUID
324
+ if "uuid" in pin_data:
325
+ pin_sexp.append([sexpdata.Symbol("uuid"), pin_data["uuid"]])
326
+
327
+ # Add effects
328
+ size = pin_data.get("size", config.defaults.font_size)
329
+ effects = [sexpdata.Symbol("effects")]
330
+ font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
331
+ effects.append(font)
332
+ justify = pin_data.get("justify", "right")
333
+ effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol(justify)])
334
+ pin_sexp.append(effects)
335
+
336
+ return pin_sexp
337
+
338
+
339
+ def _sheet_instances_to_sexp(self, sheet_instances: List[Dict[str, Any]]) -> List[Any]:
340
+ """Convert sheet_instances to S-expression."""
341
+ sexp = [sexpdata.Symbol("sheet_instances")]
342
+ for sheet in sheet_instances:
343
+ # Create: (path "/" (page "1"))
344
+ sheet_sexp = [
345
+ sexpdata.Symbol("path"),
346
+ sheet.get("path", "/"),
347
+ [sexpdata.Symbol("page"), str(sheet.get("page", "1"))],
348
+ ]
349
+ sexp.append(sheet_sexp)
350
+ return sexp
351
+
352
+