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,63 @@
1
+ """
2
+ Utility functions for parsing S-expression data.
3
+
4
+ This module contains helper functions used by various parsers
5
+ to handle common parsing patterns safely and consistently.
6
+ """
7
+
8
+ import logging
9
+ from typing import Any
10
+
11
+ import sexpdata
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def parse_bool_property(value: Any, default: bool = True) -> bool:
17
+ """
18
+ Parse a boolean property from S-expression data.
19
+
20
+ Handles both sexpdata.Symbol and string types, converting yes/no to bool.
21
+ This is the canonical way to parse boolean properties from KiCad files.
22
+
23
+ Args:
24
+ value: Value from S-expression (Symbol, str, bool, or None)
25
+ default: Default value if parsing fails or value is None
26
+
27
+ Returns:
28
+ bool: Parsed boolean value
29
+
30
+ Examples:
31
+ >>> parse_bool_property(sexpdata.Symbol('yes'))
32
+ True
33
+ >>> parse_bool_property('no')
34
+ False
35
+ >>> parse_bool_property(None, default=False)
36
+ False
37
+ >>> parse_bool_property('YES') # Case insensitive
38
+ True
39
+
40
+ Note:
41
+ This function was added to fix a critical bug where Symbol('yes') == 'yes'
42
+ returned False, causing properties like in_bom and on_board to be parsed
43
+ incorrectly.
44
+ """
45
+ # If value is None, use default
46
+ if value is None:
47
+ return default
48
+
49
+ # Convert Symbol to string
50
+ if isinstance(value, sexpdata.Symbol):
51
+ value = str(value)
52
+
53
+ # Handle string values (case-insensitive)
54
+ if isinstance(value, str):
55
+ return value.lower() == "yes"
56
+
57
+ # Handle boolean values directly
58
+ if isinstance(value, bool):
59
+ return value
60
+
61
+ # Unexpected type - use default
62
+ logger.warning(f"Unexpected type for boolean property: {type(value)}, using default={default}")
63
+ return default
@@ -66,14 +66,14 @@ def get_component_pin_position(component: SchematicSymbol, pin_number: str) -> O
66
66
 
67
67
  # Look for pin in symbol definition
68
68
  pins_found = []
69
- for pin_def in symbol_def.get("pins", []):
70
- pins_found.append(pin_def.get("number", "unknown"))
71
- if pin_def.get("number") == pin_number:
69
+ for pin_def in symbol_def.pins:
70
+ pins_found.append(pin_def.number)
71
+ if pin_def.number == pin_number:
72
72
  logger.info(f" Found pin {pin_number} in symbol definition")
73
73
 
74
74
  # Get pin position from definition
75
- pin_x = pin_def.get("x", 0)
76
- pin_y = pin_def.get("y", 0)
75
+ pin_x = pin_def.position.x
76
+ pin_y = pin_def.position.y
77
77
  logger.info(f" Symbol pin position: ({pin_x}, {pin_y})")
78
78
 
79
79
  # Apply component transformations
@@ -96,6 +96,100 @@ def get_component_pin_position(component: SchematicSymbol, pin_number: str) -> O
96
96
  return None
97
97
 
98
98
 
99
+ def get_component_pin_info(
100
+ component: SchematicSymbol, pin_number: str
101
+ ) -> Optional[Tuple[Point, float]]:
102
+ """
103
+ Get the absolute position and rotation of a component pin.
104
+
105
+ Args:
106
+ component: Component containing the pin
107
+ pin_number: Pin number to find
108
+
109
+ Returns:
110
+ Tuple of (absolute_position, absolute_rotation_degrees), or None if not found
111
+ """
112
+ logger.info(f"Getting pin info for {component.reference} pin {pin_number}")
113
+ logger.info(f" Component position: ({component.position.x}, {component.position.y})")
114
+ component_rotation = getattr(component, "rotation", 0)
115
+ logger.info(f" Component rotation: {component_rotation}°")
116
+ logger.info(f" Component mirror: {getattr(component, 'mirror', None)}")
117
+
118
+ # First check if pin is already in component data
119
+ for pin in component.pins:
120
+ if pin.number == pin_number:
121
+ logger.info(f" Found pin {pin_number} in component data")
122
+ logger.info(f" Pin relative position: ({pin.position.x}, {pin.position.y})")
123
+ logger.info(f" Pin rotation: {pin.rotation}°")
124
+
125
+ # Apply component transformations to position
126
+ absolute_pos = apply_transformation(
127
+ (pin.position.x, pin.position.y),
128
+ component.position,
129
+ component_rotation,
130
+ getattr(component, "mirror", None),
131
+ )
132
+
133
+ # Calculate absolute rotation (pin rotation + component rotation)
134
+ absolute_rotation = (pin.rotation + component_rotation) % 360
135
+
136
+ result_pos = Point(absolute_pos[0], absolute_pos[1])
137
+ logger.info(
138
+ f" Final absolute position: ({result_pos.x}, {result_pos.y}), rotation: {absolute_rotation}°"
139
+ )
140
+ return (result_pos, absolute_rotation)
141
+
142
+ # If not in component data, try to get from symbol library
143
+ logger.info(f" Pin {pin_number} not in component data, checking symbol library")
144
+
145
+ try:
146
+ symbol_cache = get_symbol_cache()
147
+ symbol_def = symbol_cache.get_symbol(component.lib_id)
148
+
149
+ if not symbol_def:
150
+ logger.warning(f" Symbol definition not found for {component.lib_id}")
151
+ return None
152
+
153
+ logger.info(f" Found symbol definition for {component.lib_id}")
154
+
155
+ # Look for pin in symbol definition
156
+ pins_found = []
157
+ for pin_def in symbol_def.pins:
158
+ pins_found.append(pin_def.number)
159
+ if pin_def.number == pin_number:
160
+ logger.info(f" Found pin {pin_number} in symbol definition")
161
+
162
+ # Get pin position and rotation from definition
163
+ pin_x = pin_def.position.x
164
+ pin_y = pin_def.position.y
165
+ pin_rotation = pin_def.rotation
166
+ logger.info(f" Symbol pin position: ({pin_x}, {pin_y}), rotation: {pin_rotation}°")
167
+
168
+ # Apply component transformations to position
169
+ absolute_pos = apply_transformation(
170
+ (pin_x, pin_y),
171
+ component.position,
172
+ component_rotation,
173
+ getattr(component, "mirror", None),
174
+ )
175
+
176
+ # Calculate absolute rotation
177
+ absolute_rotation = (pin_rotation + component_rotation) % 360
178
+
179
+ result_pos = Point(absolute_pos[0], absolute_pos[1])
180
+ logger.info(
181
+ f" Final absolute position: ({result_pos.x}, {result_pos.y}), rotation: {absolute_rotation}°"
182
+ )
183
+ return (result_pos, absolute_rotation)
184
+
185
+ logger.warning(f" Pin {pin_number} not found in symbol. Available pins: {pins_found}")
186
+
187
+ except Exception as e:
188
+ logger.error(f" Error accessing symbol cache: {e}")
189
+
190
+ return None
191
+
192
+
99
193
  def list_component_pins(component: SchematicSymbol) -> List[Tuple[str, Point]]:
100
194
  """
101
195
  List all pins for a component with their absolute positions.
@@ -127,10 +221,10 @@ def list_component_pins(component: SchematicSymbol) -> List[Tuple[str, Point]]:
127
221
  symbol_def = symbol_cache.get_symbol(component.lib_id)
128
222
 
129
223
  if symbol_def:
130
- for pin_def in symbol_def.get("pins", []):
131
- pin_number = pin_def.get("number")
132
- pin_x = pin_def.get("x", 0)
133
- pin_y = pin_def.get("y", 0)
224
+ for pin_def in symbol_def.pins:
225
+ pin_number = pin_def.number
226
+ pin_x = pin_def.position.x
227
+ pin_y = pin_def.position.y
134
228
 
135
229
  absolute_pos = apply_transformation(
136
230
  (pin_x, pin_y),