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,38 @@
1
+ """
2
+ Geometry module for KiCad schematic operations.
3
+
4
+ This module provides:
5
+ - Accurate bounding box calculations for KiCad symbols
6
+ - Orthogonal (Manhattan) routing for wire connections
7
+ - Font metrics and symbol geometry analysis
8
+
9
+ Migrated from circuit-synth to kicad-sch-api for better architectural separation.
10
+ """
11
+
12
+ from .font_metrics import (
13
+ DEFAULT_PIN_LENGTH,
14
+ DEFAULT_PIN_NAME_OFFSET,
15
+ DEFAULT_PIN_NUMBER_SIZE,
16
+ DEFAULT_PIN_TEXT_WIDTH_RATIO,
17
+ DEFAULT_TEXT_HEIGHT,
18
+ )
19
+ from .routing import (
20
+ CornerDirection,
21
+ RoutingResult,
22
+ create_orthogonal_routing,
23
+ validate_routing_result,
24
+ )
25
+ from .symbol_bbox import SymbolBoundingBoxCalculator
26
+
27
+ __all__ = [
28
+ "SymbolBoundingBoxCalculator",
29
+ "DEFAULT_TEXT_HEIGHT",
30
+ "DEFAULT_PIN_LENGTH",
31
+ "DEFAULT_PIN_NAME_OFFSET",
32
+ "DEFAULT_PIN_NUMBER_SIZE",
33
+ "DEFAULT_PIN_TEXT_WIDTH_RATIO",
34
+ "CornerDirection",
35
+ "RoutingResult",
36
+ "create_orthogonal_routing",
37
+ "validate_routing_result",
38
+ ]
@@ -0,0 +1,22 @@
1
+ """
2
+ Font metrics and text rendering constants for KiCad schematic text.
3
+
4
+ These constants are used for accurate text bounding box calculations
5
+ and symbol spacing in schematic layouts.
6
+ """
7
+
8
+ # KiCad default text size in mm
9
+ # Increased to better match actual KiCad rendering
10
+ DEFAULT_TEXT_HEIGHT = 2.54 # 100 mils (doubled from 50 mils)
11
+
12
+ # Default pin dimensions
13
+ DEFAULT_PIN_LENGTH = 2.54 # 100 mils
14
+ DEFAULT_PIN_NAME_OFFSET = 0.508 # 20 mils - offset from pin endpoint to label text
15
+ DEFAULT_PIN_NUMBER_SIZE = 1.27 # 50 mils
16
+
17
+ # Text width ratio for proportional font rendering
18
+ # KiCad uses proportional fonts where average character width is ~0.65x height
19
+ # This prevents label text from extending beyond calculated bounding boxes
20
+ DEFAULT_PIN_TEXT_WIDTH_RATIO = (
21
+ 0.65 # Width to height ratio for pin text (proportional font average)
22
+ )
@@ -0,0 +1,211 @@
1
+ """
2
+ Orthogonal routing algorithms for automatic wire routing between points.
3
+
4
+ This module provides functions for creating orthogonal (Manhattan) wire routes
5
+ between component pins, with support for direct routing when points are aligned
6
+ and L-shaped routing when they are not.
7
+
8
+ CRITICAL: KiCAD Y-axis is INVERTED (+Y is DOWN)
9
+ - Lower Y values = visually HIGHER on screen (top)
10
+ - Higher Y values = visually LOWER on screen (bottom)
11
+ """
12
+
13
+ from dataclasses import dataclass
14
+ from enum import Enum
15
+ from typing import List, Optional, Tuple
16
+
17
+ from kicad_sch_api.core.types import Point
18
+
19
+
20
+ class CornerDirection(Enum):
21
+ """Direction preference for L-shaped routing corner."""
22
+
23
+ AUTO = "auto" # Automatic selection based on distance heuristic
24
+ HORIZONTAL_FIRST = "horizontal_first" # Route horizontally, then vertically
25
+ VERTICAL_FIRST = "vertical_first" # Route vertically, then horizontally
26
+
27
+
28
+ @dataclass
29
+ class RoutingResult:
30
+ """
31
+ Result of orthogonal routing calculation.
32
+
33
+ Attributes:
34
+ segments: List of wire segments as (start, end) point tuples
35
+ corner: Corner junction point (None if direct routing)
36
+ is_direct: True if routing is a single straight line
37
+ """
38
+
39
+ segments: List[Tuple[Point, Point]]
40
+ corner: Optional[Point]
41
+ is_direct: bool
42
+
43
+
44
+ def create_orthogonal_routing(
45
+ from_pos: Point,
46
+ to_pos: Point,
47
+ corner_direction: CornerDirection = CornerDirection.AUTO
48
+ ) -> RoutingResult:
49
+ """
50
+ Create orthogonal (Manhattan) routing between two points.
51
+
52
+ Generates either direct routing (when points are aligned on same axis)
53
+ or L-shaped routing (when points require a corner).
54
+
55
+ CRITICAL: Remember KiCAD Y-axis is INVERTED:
56
+ - Lower Y values = visually HIGHER (top of screen)
57
+ - Higher Y values = visually LOWER (bottom of screen)
58
+
59
+ Args:
60
+ from_pos: Starting point
61
+ to_pos: Ending point
62
+ corner_direction: Direction preference for L-shaped corner
63
+ - AUTO: Choose based on distance heuristic (horizontal if dx >= dy)
64
+ - HORIZONTAL_FIRST: Route horizontally, then vertically
65
+ - VERTICAL_FIRST: Route vertically, then horizontally
66
+
67
+ Returns:
68
+ RoutingResult with segments list, corner point, and direct flag
69
+
70
+ Examples:
71
+ >>> # Direct horizontal routing (aligned on Y axis)
72
+ >>> result = create_orthogonal_routing(
73
+ ... Point(100, 100),
74
+ ... Point(150, 100)
75
+ ... )
76
+ >>> result.is_direct
77
+ True
78
+ >>> len(result.segments)
79
+ 1
80
+
81
+ >>> # L-shaped routing (not aligned)
82
+ >>> result = create_orthogonal_routing(
83
+ ... Point(100, 100),
84
+ ... Point(150, 125),
85
+ ... corner_direction=CornerDirection.HORIZONTAL_FIRST
86
+ ... )
87
+ >>> result.is_direct
88
+ False
89
+ >>> len(result.segments)
90
+ 2
91
+ >>> result.corner
92
+ Point(x=150.0, y=100.0)
93
+ """
94
+ # Check if points are aligned on same axis (direct routing possible)
95
+ if from_pos.x == to_pos.x or from_pos.y == to_pos.y:
96
+ # Direct line - no corner needed
97
+ return RoutingResult(
98
+ segments=[(from_pos, to_pos)],
99
+ corner=None,
100
+ is_direct=True
101
+ )
102
+
103
+ # Points are not aligned - need L-shaped routing with corner
104
+ corner = _calculate_corner_point(from_pos, to_pos, corner_direction)
105
+
106
+ return RoutingResult(
107
+ segments=[
108
+ (from_pos, corner),
109
+ (corner, to_pos)
110
+ ],
111
+ corner=corner,
112
+ is_direct=False
113
+ )
114
+
115
+
116
+ def _calculate_corner_point(
117
+ from_pos: Point,
118
+ to_pos: Point,
119
+ corner_direction: CornerDirection
120
+ ) -> Point:
121
+ """
122
+ Calculate the corner point for L-shaped routing.
123
+
124
+ Args:
125
+ from_pos: Starting point
126
+ to_pos: Ending point
127
+ corner_direction: Direction preference for corner
128
+
129
+ Returns:
130
+ Corner point position
131
+ """
132
+ if corner_direction == CornerDirection.HORIZONTAL_FIRST:
133
+ # Route horizontally first, then vertically
134
+ # Corner is at destination X, source Y
135
+ return Point(to_pos.x, from_pos.y)
136
+
137
+ elif corner_direction == CornerDirection.VERTICAL_FIRST:
138
+ # Route vertically first, then horizontally
139
+ # Corner is at source X, destination Y
140
+ return Point(from_pos.x, to_pos.y)
141
+
142
+ else: # AUTO
143
+ # Heuristic: prefer horizontal first if horizontal distance >= vertical distance
144
+ dx = abs(to_pos.x - from_pos.x)
145
+ dy = abs(to_pos.y - from_pos.y)
146
+
147
+ if dx >= dy:
148
+ # Horizontal distance is greater - route horizontally first
149
+ return Point(to_pos.x, from_pos.y)
150
+ else:
151
+ # Vertical distance is greater - route vertically first
152
+ return Point(from_pos.x, to_pos.y)
153
+
154
+
155
+ def validate_routing_result(result: RoutingResult) -> bool:
156
+ """
157
+ Validate that routing result is correct.
158
+
159
+ Checks:
160
+ - All segments are orthogonal (horizontal or vertical)
161
+ - Segments connect end-to-end
162
+ - Corner point matches segment endpoints if present
163
+
164
+ Args:
165
+ result: Routing result to validate
166
+
167
+ Returns:
168
+ True if routing is valid
169
+
170
+ Raises:
171
+ ValueError: If routing is invalid
172
+ """
173
+ if not result.segments:
174
+ raise ValueError("Routing must have at least one segment")
175
+
176
+ for start, end in result.segments:
177
+ # Check orthogonality - each segment must be horizontal OR vertical
178
+ if start.x != end.x and start.y != end.y:
179
+ raise ValueError(
180
+ f"Segment ({start}, {end}) is not orthogonal - "
181
+ f"must be horizontal (same Y) or vertical (same X)"
182
+ )
183
+
184
+ # Check segment connectivity - segments must connect end-to-end
185
+ for i in range(len(result.segments) - 1):
186
+ current_end = result.segments[i][1]
187
+ next_start = result.segments[i + 1][0]
188
+
189
+ if current_end.x != next_start.x or current_end.y != next_start.y:
190
+ raise ValueError(
191
+ f"Segments not connected: segment {i} ends at {current_end}, "
192
+ f"segment {i+1} starts at {next_start}"
193
+ )
194
+
195
+ # Check corner consistency
196
+ if result.corner is not None:
197
+ if len(result.segments) < 2:
198
+ raise ValueError("Corner specified but less than 2 segments present")
199
+
200
+ # Corner should be the endpoint of first segment and startpoint of second
201
+ first_end = result.segments[0][1]
202
+ second_start = result.segments[1][0]
203
+
204
+ if (result.corner.x != first_end.x or result.corner.y != first_end.y or
205
+ result.corner.x != second_start.x or result.corner.y != second_start.y):
206
+ raise ValueError(
207
+ f"Corner point {result.corner} does not match segment endpoints: "
208
+ f"first segment ends at {first_end}, second starts at {second_start}"
209
+ )
210
+
211
+ return True