kicad-sch-api 0.3.5__py3-none-any.whl → 0.4.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 (81) hide show
  1. kicad_sch_api/__init__.py +2 -2
  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/netlist.py +94 -0
  8. kicad_sch_api/cli/types.py +43 -0
  9. kicad_sch_api/collections/__init__.py +2 -2
  10. kicad_sch_api/collections/base.py +5 -7
  11. kicad_sch_api/collections/components.py +24 -12
  12. kicad_sch_api/collections/junctions.py +31 -43
  13. kicad_sch_api/collections/labels.py +19 -27
  14. kicad_sch_api/collections/wires.py +17 -18
  15. kicad_sch_api/core/collections/__init__.py +5 -0
  16. kicad_sch_api/core/collections/base.py +248 -0
  17. kicad_sch_api/core/component_bounds.py +5 -0
  18. kicad_sch_api/core/components.py +67 -45
  19. kicad_sch_api/core/config.py +85 -3
  20. kicad_sch_api/core/factories/__init__.py +5 -0
  21. kicad_sch_api/core/factories/element_factory.py +276 -0
  22. kicad_sch_api/core/formatter.py +3 -1
  23. kicad_sch_api/core/junctions.py +26 -75
  24. kicad_sch_api/core/labels.py +29 -53
  25. kicad_sch_api/core/managers/__init__.py +26 -0
  26. kicad_sch_api/core/managers/file_io.py +244 -0
  27. kicad_sch_api/core/managers/format_sync.py +501 -0
  28. kicad_sch_api/core/managers/graphics.py +579 -0
  29. kicad_sch_api/core/managers/metadata.py +269 -0
  30. kicad_sch_api/core/managers/sheet.py +454 -0
  31. kicad_sch_api/core/managers/text_elements.py +536 -0
  32. kicad_sch_api/core/managers/validation.py +475 -0
  33. kicad_sch_api/core/managers/wire.py +352 -0
  34. kicad_sch_api/core/nets.py +38 -43
  35. kicad_sch_api/core/no_connects.py +33 -55
  36. kicad_sch_api/core/parser.py +75 -1731
  37. kicad_sch_api/core/schematic.py +951 -1192
  38. kicad_sch_api/core/texts.py +28 -55
  39. kicad_sch_api/core/types.py +60 -22
  40. kicad_sch_api/core/wires.py +27 -75
  41. kicad_sch_api/geometry/font_metrics.py +3 -1
  42. kicad_sch_api/geometry/symbol_bbox.py +40 -21
  43. kicad_sch_api/interfaces/__init__.py +1 -1
  44. kicad_sch_api/interfaces/parser.py +1 -1
  45. kicad_sch_api/interfaces/repository.py +1 -1
  46. kicad_sch_api/interfaces/resolver.py +1 -1
  47. kicad_sch_api/parsers/__init__.py +2 -2
  48. kicad_sch_api/parsers/base.py +7 -10
  49. kicad_sch_api/parsers/elements/__init__.py +22 -0
  50. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  51. kicad_sch_api/parsers/elements/label_parser.py +194 -0
  52. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  53. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  54. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  55. kicad_sch_api/parsers/elements/symbol_parser.py +313 -0
  56. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  57. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  58. kicad_sch_api/parsers/registry.py +4 -2
  59. kicad_sch_api/parsers/utils.py +80 -0
  60. kicad_sch_api/symbols/__init__.py +1 -1
  61. kicad_sch_api/symbols/cache.py +9 -12
  62. kicad_sch_api/symbols/resolver.py +20 -26
  63. kicad_sch_api/symbols/validators.py +188 -137
  64. kicad_sch_api/validation/__init__.py +25 -0
  65. kicad_sch_api/validation/erc.py +171 -0
  66. kicad_sch_api/validation/erc_models.py +203 -0
  67. kicad_sch_api/validation/pin_matrix.py +243 -0
  68. kicad_sch_api/validation/validators.py +391 -0
  69. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
  70. kicad_sch_api-0.4.1.dist-info/RECORD +87 -0
  71. kicad_sch_api/core/manhattan_routing.py +0 -430
  72. kicad_sch_api/core/simple_manhattan.py +0 -228
  73. kicad_sch_api/core/wire_routing.py +0 -380
  74. kicad_sch_api/parsers/label_parser.py +0 -254
  75. kicad_sch_api/parsers/symbol_parser.py +0 -227
  76. kicad_sch_api/parsers/wire_parser.py +0 -99
  77. kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
  78. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
  79. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
  80. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
  81. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,194 @@
1
+ """
2
+ Label and hierarchical label elements parser for KiCAD schematics.
3
+
4
+ Handles parsing and serialization of Label and hierarchical label 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 LabelParser(BaseElementParser):
19
+ """Parser for Label and hierarchical label elements."""
20
+
21
+ def __init__(self):
22
+ """Initialize label parser."""
23
+ super().__init__("label")
24
+
25
+ def _parse_label(self, item: List[Any]) -> Optional[Dict[str, Any]]:
26
+ """Parse a label definition."""
27
+ # Label format: (label "text" (at x y rotation) (effects ...) (uuid ...))
28
+ if len(item) < 2:
29
+ return None
30
+
31
+ label_data = {
32
+ "text": str(item[1]), # Label text is second element
33
+ "position": {"x": 0, "y": 0},
34
+ "rotation": 0,
35
+ "size": config.defaults.font_size,
36
+ "uuid": None,
37
+ }
38
+
39
+ for elem in item[2:]: # Skip label keyword and text
40
+ if not isinstance(elem, list):
41
+ continue
42
+
43
+ elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
44
+
45
+ if elem_type == "at":
46
+ # Parse position: (at x y rotation)
47
+ if len(elem) >= 3:
48
+ label_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
49
+ if len(elem) >= 4:
50
+ label_data["rotation"] = float(elem[3])
51
+
52
+ elif elem_type == "effects":
53
+ # Parse effects for font size: (effects (font (size x y)) ...)
54
+ for effect_elem in elem[1:]:
55
+ if isinstance(effect_elem, list) and str(effect_elem[0]) == "font":
56
+ for font_elem in effect_elem[1:]:
57
+ if isinstance(font_elem, list) and str(font_elem[0]) == "size":
58
+ if len(font_elem) >= 2:
59
+ label_data["size"] = float(font_elem[1])
60
+
61
+ elif elem_type == "uuid":
62
+ label_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
63
+
64
+ return label_data
65
+
66
+
67
+ def _parse_hierarchical_label(self, item: List[Any]) -> Optional[Dict[str, Any]]:
68
+ """Parse a hierarchical label definition."""
69
+ # Format: (hierarchical_label "text" (shape input) (at x y rotation) (effects ...) (uuid ...))
70
+ if len(item) < 2:
71
+ return None
72
+
73
+ hlabel_data = {
74
+ "text": str(item[1]), # Hierarchical label text is second element
75
+ "shape": "input", # input/output/bidirectional/tri_state/passive
76
+ "position": {"x": 0, "y": 0},
77
+ "rotation": 0,
78
+ "size": config.defaults.font_size,
79
+ "justify": "left",
80
+ "uuid": None,
81
+ }
82
+
83
+ for elem in item[2:]: # Skip hierarchical_label keyword and text
84
+ if not isinstance(elem, list):
85
+ continue
86
+
87
+ elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
88
+
89
+ if elem_type == "shape":
90
+ # Parse shape: (shape input)
91
+ if len(elem) >= 2:
92
+ hlabel_data["shape"] = str(elem[1])
93
+
94
+ elif elem_type == "at":
95
+ # Parse position: (at x y rotation)
96
+ if len(elem) >= 3:
97
+ hlabel_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
98
+ if len(elem) >= 4:
99
+ hlabel_data["rotation"] = float(elem[3])
100
+
101
+ elif elem_type == "effects":
102
+ # Parse effects for font size and justification: (effects (font (size x y)) (justify left))
103
+ for effect_elem in elem[1:]:
104
+ if isinstance(effect_elem, list):
105
+ effect_type = (
106
+ str(effect_elem[0])
107
+ if isinstance(effect_elem[0], sexpdata.Symbol)
108
+ else None
109
+ )
110
+
111
+ if effect_type == "font":
112
+ # Parse font size
113
+ for font_elem in effect_elem[1:]:
114
+ if isinstance(font_elem, list) and str(font_elem[0]) == "size":
115
+ if len(font_elem) >= 2:
116
+ hlabel_data["size"] = float(font_elem[1])
117
+
118
+ elif effect_type == "justify":
119
+ # Parse justification (e.g., "left", "right")
120
+ if len(effect_elem) >= 2:
121
+ hlabel_data["justify"] = str(effect_elem[1])
122
+
123
+ elif elem_type == "uuid":
124
+ hlabel_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
125
+
126
+ return hlabel_data
127
+
128
+
129
+ def _label_to_sexp(self, label_data: Dict[str, Any]) -> List[Any]:
130
+ """Convert local label to S-expression."""
131
+ sexp = [sexpdata.Symbol("label"), label_data["text"]]
132
+
133
+ # Add position
134
+ pos = label_data["position"]
135
+ x, y = pos["x"], pos["y"]
136
+ rotation = label_data.get("rotation", 0)
137
+
138
+ # Format coordinates properly
139
+ if isinstance(x, float) and x.is_integer():
140
+ x = int(x)
141
+ if isinstance(y, float) and y.is_integer():
142
+ y = int(y)
143
+
144
+ sexp.append([sexpdata.Symbol("at"), x, y, rotation])
145
+
146
+ # Add effects (font properties)
147
+ size = label_data.get("size", config.defaults.font_size)
148
+ effects = [sexpdata.Symbol("effects")]
149
+ font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
150
+ effects.append(font)
151
+ effects.append(
152
+ [sexpdata.Symbol("justify"), sexpdata.Symbol("left"), sexpdata.Symbol("bottom")]
153
+ )
154
+ sexp.append(effects)
155
+
156
+ # Add UUID
157
+ if "uuid" in label_data:
158
+ sexp.append([sexpdata.Symbol("uuid"), label_data["uuid"]])
159
+
160
+ return sexp
161
+
162
+
163
+ def _hierarchical_label_to_sexp(self, hlabel_data: Dict[str, Any]) -> List[Any]:
164
+ """Convert hierarchical label to S-expression."""
165
+ sexp = [sexpdata.Symbol("hierarchical_label"), hlabel_data["text"]]
166
+
167
+ # Add shape
168
+ shape = hlabel_data.get("shape", "input")
169
+ sexp.append([sexpdata.Symbol("shape"), sexpdata.Symbol(shape)])
170
+
171
+ # Add position
172
+ pos = hlabel_data["position"]
173
+ x, y = pos["x"], pos["y"]
174
+ rotation = hlabel_data.get("rotation", 0)
175
+ sexp.append([sexpdata.Symbol("at"), x, y, rotation])
176
+
177
+ # Add effects (font properties)
178
+ size = hlabel_data.get("size", config.defaults.font_size)
179
+ effects = [sexpdata.Symbol("effects")]
180
+ font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
181
+ effects.append(font)
182
+
183
+ # Use justification from data if provided, otherwise default to "left"
184
+ justify = hlabel_data.get("justify", "left")
185
+ effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol(justify)])
186
+ sexp.append(effects)
187
+
188
+ # Add UUID
189
+ if "uuid" in hlabel_data:
190
+ sexp.append([sexpdata.Symbol("uuid"), hlabel_data["uuid"]])
191
+
192
+ return sexp
193
+
194
+
@@ -0,0 +1,165 @@
1
+ """
2
+ Symbol library definitions parser for KiCAD schematics.
3
+
4
+ Handles parsing and serialization of Symbol library definitions.
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 LibraryParser(BaseElementParser):
18
+ """Parser for Symbol library definitions."""
19
+
20
+ def __init__(self):
21
+ """Initialize library parser."""
22
+ super().__init__("library")
23
+
24
+ def _parse_lib_symbols(self, item: List[Any]) -> Dict[str, Any]:
25
+ """Parse lib_symbols section."""
26
+ # Implementation for lib_symbols parsing
27
+ return {}
28
+
29
+ # Conversion methods from internal format to S-expression
30
+
31
+ def _lib_symbols_to_sexp(self, lib_symbols: Dict[str, Any]) -> List[Any]:
32
+ """Convert lib_symbols to S-expression."""
33
+ sexp = [sexpdata.Symbol("lib_symbols")]
34
+
35
+ # Add each symbol definition
36
+ for symbol_name, symbol_def in lib_symbols.items():
37
+ if isinstance(symbol_def, list):
38
+ # Raw S-expression data from parsed library file - use directly
39
+ sexp.append(symbol_def)
40
+ elif isinstance(symbol_def, dict):
41
+ # Dictionary format - convert to S-expression
42
+ symbol_sexp = self._create_basic_symbol_definition(symbol_name)
43
+ sexp.append(symbol_sexp)
44
+
45
+ return sexp
46
+
47
+
48
+ def _create_basic_symbol_definition(self, lib_id: str) -> List[Any]:
49
+ """Create a basic symbol definition for KiCAD compatibility."""
50
+ symbol_sexp = [sexpdata.Symbol("symbol"), lib_id]
51
+
52
+ # Add basic symbol properties
53
+ symbol_sexp.extend(
54
+ [
55
+ [sexpdata.Symbol("pin_numbers"), [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")]],
56
+ [sexpdata.Symbol("pin_names"), [sexpdata.Symbol("offset"), 0]],
57
+ [sexpdata.Symbol("exclude_from_sim"), sexpdata.Symbol("no")],
58
+ [sexpdata.Symbol("in_bom"), sexpdata.Symbol("yes")],
59
+ [sexpdata.Symbol("on_board"), sexpdata.Symbol("yes")],
60
+ ]
61
+ )
62
+
63
+ # Add basic properties for the symbol
64
+ if "R" in lib_id: # Resistor
65
+ symbol_sexp.extend(
66
+ [
67
+ [
68
+ sexpdata.Symbol("property"),
69
+ "Reference",
70
+ "R",
71
+ [sexpdata.Symbol("at"), 2.032, 0, 90],
72
+ [
73
+ sexpdata.Symbol("effects"),
74
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
75
+ ],
76
+ ],
77
+ [
78
+ sexpdata.Symbol("property"),
79
+ "Value",
80
+ "R",
81
+ [sexpdata.Symbol("at"), 0, 0, 90],
82
+ [
83
+ sexpdata.Symbol("effects"),
84
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
85
+ ],
86
+ ],
87
+ [
88
+ sexpdata.Symbol("property"),
89
+ "Footprint",
90
+ "",
91
+ [sexpdata.Symbol("at"), -1.778, 0, 90],
92
+ [
93
+ sexpdata.Symbol("effects"),
94
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
95
+ [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")],
96
+ ],
97
+ ],
98
+ [
99
+ sexpdata.Symbol("property"),
100
+ "Datasheet",
101
+ "~",
102
+ [sexpdata.Symbol("at"), 0, 0, 0],
103
+ [
104
+ sexpdata.Symbol("effects"),
105
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
106
+ [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")],
107
+ ],
108
+ ],
109
+ ]
110
+ )
111
+
112
+ elif "C" in lib_id: # Capacitor
113
+ symbol_sexp.extend(
114
+ [
115
+ [
116
+ sexpdata.Symbol("property"),
117
+ "Reference",
118
+ "C",
119
+ [sexpdata.Symbol("at"), 0.635, 2.54, 0],
120
+ [
121
+ sexpdata.Symbol("effects"),
122
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
123
+ ],
124
+ ],
125
+ [
126
+ sexpdata.Symbol("property"),
127
+ "Value",
128
+ "C",
129
+ [sexpdata.Symbol("at"), 0.635, -2.54, 0],
130
+ [
131
+ sexpdata.Symbol("effects"),
132
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
133
+ ],
134
+ ],
135
+ [
136
+ sexpdata.Symbol("property"),
137
+ "Footprint",
138
+ "",
139
+ [sexpdata.Symbol("at"), 0, -1.27, 0],
140
+ [
141
+ sexpdata.Symbol("effects"),
142
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
143
+ [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")],
144
+ ],
145
+ ],
146
+ [
147
+ sexpdata.Symbol("property"),
148
+ "Datasheet",
149
+ "~",
150
+ [sexpdata.Symbol("at"), 0, 0, 0],
151
+ [
152
+ sexpdata.Symbol("effects"),
153
+ [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
154
+ [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")],
155
+ ],
156
+ ],
157
+ ]
158
+ )
159
+
160
+ # Add basic graphics and pins (minimal for now)
161
+ symbol_sexp.append([sexpdata.Symbol("embedded_fonts"), sexpdata.Symbol("no")])
162
+
163
+ return symbol_sexp
164
+
165
+
@@ -0,0 +1,58 @@
1
+ """
2
+ Title block and symbol instances parser for KiCAD schematics.
3
+
4
+ Handles parsing and serialization of Title block and symbol instances.
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 MetadataParser(BaseElementParser):
18
+ """Parser for Title block and symbol instances."""
19
+
20
+ def __init__(self):
21
+ """Initialize metadata parser."""
22
+ super().__init__("metadata")
23
+
24
+ def _parse_title_block(self, item: List[Any]) -> Dict[str, Any]:
25
+ """Parse title block information."""
26
+ title_block = {}
27
+ for sub_item in item[1:]:
28
+ if isinstance(sub_item, list) and len(sub_item) >= 2:
29
+ key = str(sub_item[0]) if isinstance(sub_item[0], sexpdata.Symbol) else None
30
+ if key:
31
+ title_block[key] = sub_item[1] if len(sub_item) > 1 else None
32
+ return title_block
33
+
34
+
35
+ def _parse_symbol_instances(self, item: List[Any]) -> List[Any]:
36
+ """Parse symbol_instances section."""
37
+ # For now, just return the raw structure minus the header
38
+ return item[1:] if len(item) > 1 else []
39
+
40
+
41
+ def _title_block_to_sexp(self, title_block: Dict[str, Any]) -> List[Any]:
42
+ """Convert title block to S-expression."""
43
+ sexp = [sexpdata.Symbol("title_block")]
44
+
45
+ # Add standard fields
46
+ for key in ["title", "date", "rev", "company"]:
47
+ if key in title_block and title_block[key]:
48
+ sexp.append([sexpdata.Symbol(key), title_block[key]])
49
+
50
+ # Add comments with special formatting
51
+ comments = title_block.get("comments", {})
52
+ if isinstance(comments, dict):
53
+ for comment_num, comment_text in comments.items():
54
+ sexp.append([sexpdata.Symbol("comment"), comment_num, comment_text])
55
+
56
+ return sexp
57
+
58
+