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.
- kicad_sch_api/__init__.py +68 -3
- kicad_sch_api/cli/__init__.py +45 -0
- kicad_sch_api/cli/base.py +302 -0
- kicad_sch_api/cli/bom.py +164 -0
- kicad_sch_api/cli/erc.py +229 -0
- kicad_sch_api/cli/export_docs.py +289 -0
- kicad_sch_api/cli/kicad_to_python.py +169 -0
- kicad_sch_api/cli/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +36 -0
- kicad_sch_api/collections/base.py +604 -0
- kicad_sch_api/collections/components.py +1623 -0
- kicad_sch_api/collections/junctions.py +206 -0
- kicad_sch_api/collections/labels.py +508 -0
- kicad_sch_api/collections/wires.py +292 -0
- kicad_sch_api/core/__init__.py +37 -2
- kicad_sch_api/core/collections/__init__.py +5 -0
- kicad_sch_api/core/collections/base.py +248 -0
- kicad_sch_api/core/component_bounds.py +34 -7
- kicad_sch_api/core/components.py +213 -52
- kicad_sch_api/core/config.py +110 -15
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +278 -0
- kicad_sch_api/core/formatter.py +60 -9
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +324 -0
- kicad_sch_api/core/managers/__init__.py +30 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +246 -0
- kicad_sch_api/core/managers/format_sync.py +502 -0
- kicad_sch_api/core/managers/graphics.py +580 -0
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +271 -0
- kicad_sch_api/core/managers/sheet.py +492 -0
- kicad_sch_api/core/managers/text_elements.py +537 -0
- kicad_sch_api/core/managers/validation.py +476 -0
- kicad_sch_api/core/managers/wire.py +410 -0
- kicad_sch_api/core/nets.py +305 -0
- kicad_sch_api/core/no_connects.py +252 -0
- kicad_sch_api/core/parser.py +194 -970
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +1328 -1079
- kicad_sch_api/core/texts.py +316 -0
- kicad_sch_api/core/types.py +159 -23
- kicad_sch_api/core/wires.py +27 -75
- kicad_sch_api/exporters/__init__.py +10 -0
- kicad_sch_api/exporters/python_generator.py +610 -0
- kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
- kicad_sch_api/geometry/__init__.py +38 -0
- kicad_sch_api/geometry/font_metrics.py +22 -0
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/geometry/symbol_bbox.py +608 -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 +145 -0
- kicad_sch_api/parsers/elements/__init__.py +22 -0
- kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
- kicad_sch_api/parsers/elements/label_parser.py +216 -0
- kicad_sch_api/parsers/elements/library_parser.py +165 -0
- kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
- kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
- kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
- kicad_sch_api/parsers/elements/text_parser.py +250 -0
- kicad_sch_api/parsers/elements/wire_parser.py +242 -0
- kicad_sch_api/parsers/registry.py +155 -0
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api/symbols/validators.py +504 -0
- kicad_sch_api/utils/logging.py +555 -0
- kicad_sch_api/utils/logging_decorators.py +587 -0
- kicad_sch_api/utils/validation.py +16 -22
- kicad_sch_api/validation/__init__.py +25 -0
- kicad_sch_api/validation/erc.py +171 -0
- kicad_sch_api/validation/erc_models.py +203 -0
- kicad_sch_api/validation/pin_matrix.py +243 -0
- kicad_sch_api/validation/validators.py +391 -0
- kicad_sch_api/wrappers/__init__.py +14 -0
- kicad_sch_api/wrappers/base.py +89 -0
- kicad_sch_api/wrappers/wire.py +198 -0
- kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
- kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
- kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
- mcp_server/__init__.py +34 -0
- mcp_server/example_logging_integration.py +506 -0
- mcp_server/models.py +252 -0
- mcp_server/server.py +357 -0
- mcp_server/tools/__init__.py +32 -0
- mcp_server/tools/component_tools.py +516 -0
- mcp_server/tools/connectivity_tools.py +532 -0
- mcp_server/tools/consolidated_tools.py +1216 -0
- mcp_server/tools/pin_discovery.py +333 -0
- mcp_server/utils/__init__.py +38 -0
- mcp_server/utils/logging.py +127 -0
- mcp_server/utils.py +36 -0
- kicad_sch_api/core/manhattan_routing.py +0 -430
- kicad_sch_api/core/simple_manhattan.py +0 -228
- kicad_sch_api/core/wire_routing.py +0 -380
- kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
- kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
- kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {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
|