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
kicad_sch_api/cli/erc.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Electrical Rule Check (ERC) functionality using kicad-cli."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from kicad_sch_api.cli.base import KiCadExecutor
|
|
9
|
+
from kicad_sch_api.cli.types import ErcFormat, ErcSeverity, Units
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ErcViolation:
|
|
14
|
+
"""Represents a single ERC violation."""
|
|
15
|
+
severity: str
|
|
16
|
+
type: str
|
|
17
|
+
description: str
|
|
18
|
+
sheet: str
|
|
19
|
+
position: Optional[Dict[str, float]] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ErcReport:
|
|
24
|
+
"""ERC report with violations and summary."""
|
|
25
|
+
violations: List[ErcViolation]
|
|
26
|
+
error_count: int
|
|
27
|
+
warning_count: int
|
|
28
|
+
exclusion_count: int
|
|
29
|
+
schematic_path: Path
|
|
30
|
+
raw_output: str
|
|
31
|
+
|
|
32
|
+
def has_errors(self) -> bool:
|
|
33
|
+
"""Check if report contains any errors."""
|
|
34
|
+
return self.error_count > 0
|
|
35
|
+
|
|
36
|
+
def has_warnings(self) -> bool:
|
|
37
|
+
"""Check if report contains any warnings."""
|
|
38
|
+
return self.warning_count > 0
|
|
39
|
+
|
|
40
|
+
def has_violations(self) -> bool:
|
|
41
|
+
"""Check if report contains any violations."""
|
|
42
|
+
return len(self.violations) > 0
|
|
43
|
+
|
|
44
|
+
def get_errors(self) -> List[ErcViolation]:
|
|
45
|
+
"""Get all error-level violations."""
|
|
46
|
+
return [v for v in self.violations if v.severity == "error"]
|
|
47
|
+
|
|
48
|
+
def get_warnings(self) -> List[ErcViolation]:
|
|
49
|
+
"""Get all warning-level violations."""
|
|
50
|
+
return [v for v in self.violations if v.severity == "warning"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_erc(
|
|
54
|
+
schematic_path: Path,
|
|
55
|
+
output_path: Optional[Path] = None,
|
|
56
|
+
format: ErcFormat = "json",
|
|
57
|
+
severity: ErcSeverity = "all",
|
|
58
|
+
units: Units = "mm",
|
|
59
|
+
exit_code_violations: bool = False,
|
|
60
|
+
variables: Optional[Dict[str, str]] = None,
|
|
61
|
+
executor: Optional[KiCadExecutor] = None,
|
|
62
|
+
) -> ErcReport:
|
|
63
|
+
"""
|
|
64
|
+
Run Electrical Rule Check (ERC) on schematic using kicad-cli.
|
|
65
|
+
|
|
66
|
+
Validates schematic for electrical errors like unconnected pins,
|
|
67
|
+
conflicting drivers, etc.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
schematic_path: Path to .kicad_sch file
|
|
71
|
+
output_path: Output report path (auto-generated if None)
|
|
72
|
+
format: Report format ('json' or 'report')
|
|
73
|
+
severity: Severity levels to report ('all', 'error', 'warning', 'exclusions')
|
|
74
|
+
units: Measurement units for coordinates ('mm', 'in', 'mils')
|
|
75
|
+
exit_code_violations: Return non-zero exit code if violations exist
|
|
76
|
+
variables: Project variables to override (key=value pairs)
|
|
77
|
+
executor: Custom KiCadExecutor instance (creates default if None)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
ErcReport with violations and summary
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
RuntimeError: If kicad-cli not found or ERC fails
|
|
84
|
+
FileNotFoundError: If schematic file doesn't exist
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
>>> from pathlib import Path
|
|
88
|
+
>>> report = run_erc(Path('circuit.kicad_sch'))
|
|
89
|
+
>>> if report.has_errors():
|
|
90
|
+
... print(f"Found {report.error_count} errors:")
|
|
91
|
+
... for error in report.get_errors():
|
|
92
|
+
... print(f" - {error.description}")
|
|
93
|
+
|
|
94
|
+
>>> # Check for specific severity
|
|
95
|
+
>>> report = run_erc(
|
|
96
|
+
... Path('circuit.kicad_sch'),
|
|
97
|
+
... severity='error', # Only errors
|
|
98
|
+
... )
|
|
99
|
+
|
|
100
|
+
>>> # Generate human-readable report
|
|
101
|
+
>>> report = run_erc(
|
|
102
|
+
... Path('circuit.kicad_sch'),
|
|
103
|
+
... format='report',
|
|
104
|
+
... )
|
|
105
|
+
>>> print(report.raw_output)
|
|
106
|
+
"""
|
|
107
|
+
schematic_path = Path(schematic_path)
|
|
108
|
+
|
|
109
|
+
if not schematic_path.exists():
|
|
110
|
+
raise FileNotFoundError(f"Schematic not found: {schematic_path}")
|
|
111
|
+
|
|
112
|
+
# Auto-generate output path if not provided
|
|
113
|
+
if output_path is None:
|
|
114
|
+
ext = ".json" if format == "json" else ".rpt"
|
|
115
|
+
output_path = schematic_path.with_stem(f"{schematic_path.stem}_erc").with_suffix(ext)
|
|
116
|
+
else:
|
|
117
|
+
output_path = Path(output_path)
|
|
118
|
+
|
|
119
|
+
# Create executor if not provided
|
|
120
|
+
if executor is None:
|
|
121
|
+
executor = KiCadExecutor()
|
|
122
|
+
|
|
123
|
+
# Build command
|
|
124
|
+
args = [
|
|
125
|
+
"sch", "erc",
|
|
126
|
+
"--output", str(output_path),
|
|
127
|
+
"--format", format,
|
|
128
|
+
"--units", units,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# Add severity flags
|
|
132
|
+
if severity == "all":
|
|
133
|
+
args.append("--severity-all")
|
|
134
|
+
elif severity == "error":
|
|
135
|
+
args.append("--severity-error")
|
|
136
|
+
elif severity == "warning":
|
|
137
|
+
args.append("--severity-warning")
|
|
138
|
+
elif severity == "exclusions":
|
|
139
|
+
args.append("--severity-exclusions")
|
|
140
|
+
|
|
141
|
+
# Add optional parameters
|
|
142
|
+
if exit_code_violations:
|
|
143
|
+
args.append("--exit-code-violations")
|
|
144
|
+
|
|
145
|
+
if variables:
|
|
146
|
+
for key, value in variables.items():
|
|
147
|
+
args.extend(["--define-var", f"{key}={value}"])
|
|
148
|
+
|
|
149
|
+
# Add schematic path
|
|
150
|
+
args.append(str(schematic_path))
|
|
151
|
+
|
|
152
|
+
# Execute command (don't check return code if exit_code_violations is True)
|
|
153
|
+
result = executor.run(args, cwd=schematic_path.parent, check=not exit_code_violations)
|
|
154
|
+
|
|
155
|
+
# Read output file
|
|
156
|
+
output_content = output_path.read_text()
|
|
157
|
+
|
|
158
|
+
# Parse report
|
|
159
|
+
if format == "json":
|
|
160
|
+
report = _parse_json_report(output_content, schematic_path)
|
|
161
|
+
else:
|
|
162
|
+
report = _parse_text_report(output_content, schematic_path)
|
|
163
|
+
|
|
164
|
+
return report
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _parse_json_report(content: str, schematic_path: Path) -> ErcReport:
|
|
168
|
+
"""Parse JSON ERC report."""
|
|
169
|
+
try:
|
|
170
|
+
data = json.loads(content)
|
|
171
|
+
except json.JSONDecodeError:
|
|
172
|
+
# If JSON parsing fails, return empty report
|
|
173
|
+
return ErcReport(
|
|
174
|
+
violations=[],
|
|
175
|
+
error_count=0,
|
|
176
|
+
warning_count=0,
|
|
177
|
+
exclusion_count=0,
|
|
178
|
+
schematic_path=schematic_path,
|
|
179
|
+
raw_output=content,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
violations = []
|
|
183
|
+
error_count = 0
|
|
184
|
+
warning_count = 0
|
|
185
|
+
exclusion_count = 0
|
|
186
|
+
|
|
187
|
+
# Parse violations from JSON
|
|
188
|
+
for violation_data in data.get("violations", []):
|
|
189
|
+
violation = ErcViolation(
|
|
190
|
+
severity=violation_data.get("severity", "unknown"),
|
|
191
|
+
type=violation_data.get("type", "unknown"),
|
|
192
|
+
description=violation_data.get("description", ""),
|
|
193
|
+
sheet=violation_data.get("sheet", ""),
|
|
194
|
+
position=violation_data.get("position"),
|
|
195
|
+
)
|
|
196
|
+
violations.append(violation)
|
|
197
|
+
|
|
198
|
+
if violation.severity == "error":
|
|
199
|
+
error_count += 1
|
|
200
|
+
elif violation.severity == "warning":
|
|
201
|
+
warning_count += 1
|
|
202
|
+
elif violation.severity == "exclusion":
|
|
203
|
+
exclusion_count += 1
|
|
204
|
+
|
|
205
|
+
return ErcReport(
|
|
206
|
+
violations=violations,
|
|
207
|
+
error_count=error_count,
|
|
208
|
+
warning_count=warning_count,
|
|
209
|
+
exclusion_count=exclusion_count,
|
|
210
|
+
schematic_path=schematic_path,
|
|
211
|
+
raw_output=content,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _parse_text_report(content: str, schematic_path: Path) -> ErcReport:
|
|
216
|
+
"""Parse text ERC report."""
|
|
217
|
+
# For text reports, do simple counting
|
|
218
|
+
lines = content.split("\n")
|
|
219
|
+
error_count = sum(1 for line in lines if "error" in line.lower())
|
|
220
|
+
warning_count = sum(1 for line in lines if "warning" in line.lower())
|
|
221
|
+
|
|
222
|
+
return ErcReport(
|
|
223
|
+
violations=[], # Text format doesn't provide structured violations
|
|
224
|
+
error_count=error_count,
|
|
225
|
+
warning_count=warning_count,
|
|
226
|
+
exclusion_count=0,
|
|
227
|
+
schematic_path=schematic_path,
|
|
228
|
+
raw_output=content,
|
|
229
|
+
)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Document export functionality (PDF, SVG, DXF) using kicad-cli."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from kicad_sch_api.cli.base import KiCadExecutor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def export_pdf(
|
|
10
|
+
schematic_path: Path,
|
|
11
|
+
output_path: Optional[Path] = None,
|
|
12
|
+
theme: Optional[str] = None,
|
|
13
|
+
black_and_white: bool = False,
|
|
14
|
+
drawing_sheet: Optional[Path] = None,
|
|
15
|
+
exclude_drawing_sheet: bool = False,
|
|
16
|
+
default_font: str = "KiCad Font",
|
|
17
|
+
exclude_pdf_property_popups: bool = False,
|
|
18
|
+
exclude_pdf_hierarchical_links: bool = False,
|
|
19
|
+
exclude_pdf_metadata: bool = False,
|
|
20
|
+
no_background_color: bool = False,
|
|
21
|
+
pages: Optional[List[int]] = None,
|
|
22
|
+
variables: Optional[Dict[str, str]] = None,
|
|
23
|
+
executor: Optional[KiCadExecutor] = None,
|
|
24
|
+
) -> Path:
|
|
25
|
+
"""
|
|
26
|
+
Export schematic as PDF using kicad-cli.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
schematic_path: Path to .kicad_sch file
|
|
30
|
+
output_path: Output PDF path (auto-generated if None)
|
|
31
|
+
theme: Color theme to use (default: schematic settings)
|
|
32
|
+
black_and_white: Export in black and white
|
|
33
|
+
drawing_sheet: Path to custom drawing sheet
|
|
34
|
+
exclude_drawing_sheet: Don't include drawing sheet
|
|
35
|
+
default_font: Default font name (default: "KiCad Font")
|
|
36
|
+
exclude_pdf_property_popups: Don't generate property popups
|
|
37
|
+
exclude_pdf_hierarchical_links: Don't generate clickable hierarchical links
|
|
38
|
+
exclude_pdf_metadata: Don't generate PDF metadata from variables
|
|
39
|
+
no_background_color: Don't set background color
|
|
40
|
+
pages: List of page numbers to export (None = all pages)
|
|
41
|
+
variables: Project variables to override
|
|
42
|
+
executor: Custom KiCadExecutor instance
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Path to generated PDF file
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> from pathlib import Path
|
|
49
|
+
>>> pdf = export_pdf(
|
|
50
|
+
... Path('circuit.kicad_sch'),
|
|
51
|
+
... theme='KiCad Classic',
|
|
52
|
+
... )
|
|
53
|
+
"""
|
|
54
|
+
schematic_path = Path(schematic_path)
|
|
55
|
+
|
|
56
|
+
if not schematic_path.exists():
|
|
57
|
+
raise FileNotFoundError(f"Schematic not found: {schematic_path}")
|
|
58
|
+
|
|
59
|
+
if output_path is None:
|
|
60
|
+
output_path = schematic_path.with_suffix(".pdf")
|
|
61
|
+
else:
|
|
62
|
+
output_path = Path(output_path)
|
|
63
|
+
|
|
64
|
+
if executor is None:
|
|
65
|
+
executor = KiCadExecutor()
|
|
66
|
+
|
|
67
|
+
# Build command
|
|
68
|
+
args = ["sch", "export", "pdf", "--output", str(output_path)]
|
|
69
|
+
|
|
70
|
+
if theme:
|
|
71
|
+
args.extend(["--theme", theme])
|
|
72
|
+
|
|
73
|
+
if black_and_white:
|
|
74
|
+
args.append("--black-and-white")
|
|
75
|
+
|
|
76
|
+
if drawing_sheet:
|
|
77
|
+
args.extend(["--drawing-sheet", str(drawing_sheet)])
|
|
78
|
+
|
|
79
|
+
if exclude_drawing_sheet:
|
|
80
|
+
args.append("--exclude-drawing-sheet")
|
|
81
|
+
|
|
82
|
+
args.extend(["--default-font", default_font])
|
|
83
|
+
|
|
84
|
+
if exclude_pdf_property_popups:
|
|
85
|
+
args.append("--exclude-pdf-property-popups")
|
|
86
|
+
|
|
87
|
+
if exclude_pdf_hierarchical_links:
|
|
88
|
+
args.append("--exclude-pdf-hierarchical-links")
|
|
89
|
+
|
|
90
|
+
if exclude_pdf_metadata:
|
|
91
|
+
args.append("--exclude-pdf-metadata")
|
|
92
|
+
|
|
93
|
+
if no_background_color:
|
|
94
|
+
args.append("--no-background-color")
|
|
95
|
+
|
|
96
|
+
if pages:
|
|
97
|
+
args.extend(["--pages", ",".join(map(str, pages))])
|
|
98
|
+
|
|
99
|
+
if variables:
|
|
100
|
+
for key, value in variables.items():
|
|
101
|
+
args.extend(["--define-var", f"{key}={value}"])
|
|
102
|
+
|
|
103
|
+
args.append(str(schematic_path))
|
|
104
|
+
|
|
105
|
+
# Execute command
|
|
106
|
+
executor.run(args, cwd=schematic_path.parent)
|
|
107
|
+
|
|
108
|
+
return output_path
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def export_svg(
|
|
112
|
+
schematic_path: Path,
|
|
113
|
+
output_dir: Optional[Path] = None,
|
|
114
|
+
theme: Optional[str] = None,
|
|
115
|
+
black_and_white: bool = False,
|
|
116
|
+
drawing_sheet: Optional[Path] = None,
|
|
117
|
+
exclude_drawing_sheet: bool = False,
|
|
118
|
+
default_font: str = "KiCad Font",
|
|
119
|
+
no_background_color: bool = False,
|
|
120
|
+
pages: Optional[List[int]] = None,
|
|
121
|
+
variables: Optional[Dict[str, str]] = None,
|
|
122
|
+
executor: Optional[KiCadExecutor] = None,
|
|
123
|
+
) -> List[Path]:
|
|
124
|
+
"""
|
|
125
|
+
Export schematic as SVG using kicad-cli.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
schematic_path: Path to .kicad_sch file
|
|
129
|
+
output_dir: Output directory (default: schematic directory)
|
|
130
|
+
theme: Color theme to use (default: schematic settings)
|
|
131
|
+
black_and_white: Export in black and white
|
|
132
|
+
drawing_sheet: Path to custom drawing sheet
|
|
133
|
+
exclude_drawing_sheet: Don't include drawing sheet
|
|
134
|
+
default_font: Default font name (default: "KiCad Font")
|
|
135
|
+
no_background_color: Don't set background color
|
|
136
|
+
pages: List of page numbers to export (None = all pages)
|
|
137
|
+
variables: Project variables to override
|
|
138
|
+
executor: Custom KiCadExecutor instance
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of paths to generated SVG files
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> from pathlib import Path
|
|
145
|
+
>>> svgs = export_svg(
|
|
146
|
+
... Path('circuit.kicad_sch'),
|
|
147
|
+
... theme='KiCad Classic',
|
|
148
|
+
... )
|
|
149
|
+
>>> for svg in svgs:
|
|
150
|
+
... print(f"Generated: {svg}")
|
|
151
|
+
"""
|
|
152
|
+
schematic_path = Path(schematic_path)
|
|
153
|
+
|
|
154
|
+
if not schematic_path.exists():
|
|
155
|
+
raise FileNotFoundError(f"Schematic not found: {schematic_path}")
|
|
156
|
+
|
|
157
|
+
if output_dir is None:
|
|
158
|
+
output_dir = schematic_path.parent
|
|
159
|
+
else:
|
|
160
|
+
output_dir = Path(output_dir)
|
|
161
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
if executor is None:
|
|
164
|
+
executor = KiCadExecutor()
|
|
165
|
+
|
|
166
|
+
# Build command
|
|
167
|
+
args = ["sch", "export", "svg", "--output", str(output_dir)]
|
|
168
|
+
|
|
169
|
+
if theme:
|
|
170
|
+
args.extend(["--theme", theme])
|
|
171
|
+
|
|
172
|
+
if black_and_white:
|
|
173
|
+
args.append("--black-and-white")
|
|
174
|
+
|
|
175
|
+
if drawing_sheet:
|
|
176
|
+
args.extend(["--drawing-sheet", str(drawing_sheet)])
|
|
177
|
+
|
|
178
|
+
if exclude_drawing_sheet:
|
|
179
|
+
args.append("--exclude-drawing-sheet")
|
|
180
|
+
|
|
181
|
+
args.extend(["--default-font", default_font])
|
|
182
|
+
|
|
183
|
+
if no_background_color:
|
|
184
|
+
args.append("--no-background-color")
|
|
185
|
+
|
|
186
|
+
if pages:
|
|
187
|
+
args.extend(["--pages", ",".join(map(str, pages))])
|
|
188
|
+
|
|
189
|
+
if variables:
|
|
190
|
+
for key, value in variables.items():
|
|
191
|
+
args.extend(["--define-var", f"{key}={value}"])
|
|
192
|
+
|
|
193
|
+
args.append(str(schematic_path))
|
|
194
|
+
|
|
195
|
+
# Execute command
|
|
196
|
+
executor.run(args, cwd=schematic_path.parent)
|
|
197
|
+
|
|
198
|
+
# Find generated SVG files
|
|
199
|
+
svg_files = list(output_dir.glob(f"{schematic_path.stem}*.svg"))
|
|
200
|
+
|
|
201
|
+
return svg_files
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def export_dxf(
|
|
205
|
+
schematic_path: Path,
|
|
206
|
+
output_dir: Optional[Path] = None,
|
|
207
|
+
theme: Optional[str] = None,
|
|
208
|
+
black_and_white: bool = False,
|
|
209
|
+
drawing_sheet: Optional[Path] = None,
|
|
210
|
+
exclude_drawing_sheet: bool = False,
|
|
211
|
+
default_font: str = "KiCad Font",
|
|
212
|
+
no_background_color: bool = False,
|
|
213
|
+
pages: Optional[List[int]] = None,
|
|
214
|
+
variables: Optional[Dict[str, str]] = None,
|
|
215
|
+
executor: Optional[KiCadExecutor] = None,
|
|
216
|
+
) -> List[Path]:
|
|
217
|
+
"""
|
|
218
|
+
Export schematic as DXF using kicad-cli.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
schematic_path: Path to .kicad_sch file
|
|
222
|
+
output_dir: Output directory (default: schematic directory)
|
|
223
|
+
theme: Color theme to use (default: schematic settings)
|
|
224
|
+
black_and_white: Export in black and white
|
|
225
|
+
drawing_sheet: Path to custom drawing sheet
|
|
226
|
+
exclude_drawing_sheet: Don't include drawing sheet
|
|
227
|
+
default_font: Default font name (default: "KiCad Font")
|
|
228
|
+
no_background_color: Don't set background color
|
|
229
|
+
pages: List of page numbers to export (None = all pages)
|
|
230
|
+
variables: Project variables to override
|
|
231
|
+
executor: Custom KiCadExecutor instance
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
List of paths to generated DXF files
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> from pathlib import Path
|
|
238
|
+
>>> dxfs = export_dxf(Path('circuit.kicad_sch'))
|
|
239
|
+
"""
|
|
240
|
+
schematic_path = Path(schematic_path)
|
|
241
|
+
|
|
242
|
+
if not schematic_path.exists():
|
|
243
|
+
raise FileNotFoundError(f"Schematic not found: {schematic_path}")
|
|
244
|
+
|
|
245
|
+
if output_dir is None:
|
|
246
|
+
output_dir = schematic_path.parent
|
|
247
|
+
else:
|
|
248
|
+
output_dir = Path(output_dir)
|
|
249
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
|
|
251
|
+
if executor is None:
|
|
252
|
+
executor = KiCadExecutor()
|
|
253
|
+
|
|
254
|
+
# Build command
|
|
255
|
+
args = ["sch", "export", "dxf", "--output", str(output_dir)]
|
|
256
|
+
|
|
257
|
+
if theme:
|
|
258
|
+
args.extend(["--theme", theme])
|
|
259
|
+
|
|
260
|
+
if black_and_white:
|
|
261
|
+
args.append("--black-and-white")
|
|
262
|
+
|
|
263
|
+
if drawing_sheet:
|
|
264
|
+
args.extend(["--drawing-sheet", str(drawing_sheet)])
|
|
265
|
+
|
|
266
|
+
if exclude_drawing_sheet:
|
|
267
|
+
args.append("--exclude-drawing-sheet")
|
|
268
|
+
|
|
269
|
+
args.extend(["--default-font", default_font])
|
|
270
|
+
|
|
271
|
+
if no_background_color:
|
|
272
|
+
args.append("--no-background-color")
|
|
273
|
+
|
|
274
|
+
if pages:
|
|
275
|
+
args.extend(["--pages", ",".join(map(str, pages))])
|
|
276
|
+
|
|
277
|
+
if variables:
|
|
278
|
+
for key, value in variables.items():
|
|
279
|
+
args.extend(["--define-var", f"{key}={value}"])
|
|
280
|
+
|
|
281
|
+
args.append(str(schematic_path))
|
|
282
|
+
|
|
283
|
+
# Execute command
|
|
284
|
+
executor.run(args, cwd=schematic_path.parent)
|
|
285
|
+
|
|
286
|
+
# Find generated DXF files
|
|
287
|
+
dxf_files = list(output_dir.glob(f"{schematic_path.stem}*.dxf"))
|
|
288
|
+
|
|
289
|
+
return dxf_files
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KiCad-to-Python CLI Command
|
|
4
|
+
|
|
5
|
+
Convert KiCad schematic files to executable Python code.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
kicad-to-python input.kicad_sch output.py
|
|
9
|
+
kicad-to-python input.kicad_sch output.py --template verbose
|
|
10
|
+
kicad-to-python project.kicad_pro output_dir/ --include-hierarchy
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
import kicad_sch_api as ksa
|
|
19
|
+
from kicad_sch_api.exporters.python_generator import (
|
|
20
|
+
PythonCodeGenerator,
|
|
21
|
+
CodeGenerationError,
|
|
22
|
+
TemplateNotFoundError
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(argv: Optional[list] = None) -> int:
|
|
27
|
+
"""
|
|
28
|
+
Main CLI entry point.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
argv: Command-line arguments (None = sys.argv)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Exit code (0 = success, 1 = error)
|
|
35
|
+
"""
|
|
36
|
+
parser = argparse.ArgumentParser(
|
|
37
|
+
prog='kicad-to-python',
|
|
38
|
+
description='Convert KiCad schematics to Python code',
|
|
39
|
+
epilog='For more information: https://github.com/circuit-synth/kicad-sch-api'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Positional arguments
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
'input',
|
|
45
|
+
type=Path,
|
|
46
|
+
help='Input KiCad schematic (.kicad_sch) or project (.kicad_pro)'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
'output',
|
|
51
|
+
type=Path,
|
|
52
|
+
help='Output Python file (.py) or directory (for hierarchical)'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Options
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
'--template',
|
|
58
|
+
choices=['minimal', 'default', 'verbose', 'documented'],
|
|
59
|
+
default='default',
|
|
60
|
+
help='Code template style (default: default)'
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
'--include-hierarchy',
|
|
65
|
+
action='store_true',
|
|
66
|
+
help='Include hierarchical sheets'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
'--no-format',
|
|
71
|
+
action='store_true',
|
|
72
|
+
help='Skip Black code formatting'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
'--no-comments',
|
|
77
|
+
action='store_true',
|
|
78
|
+
help='Skip explanatory comments'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
'--verbose', '-v',
|
|
83
|
+
action='store_true',
|
|
84
|
+
help='Verbose output'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
args = parser.parse_args(argv)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
# Validate input file
|
|
91
|
+
if not args.input.exists():
|
|
92
|
+
print(f"❌ Error: Input file not found: {args.input}", file=sys.stderr)
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
if args.input.suffix not in ['.kicad_sch', '.kicad_pro']:
|
|
96
|
+
print(
|
|
97
|
+
f"❌ Error: Input must be .kicad_sch or .kicad_pro file",
|
|
98
|
+
file=sys.stderr
|
|
99
|
+
)
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
# Load schematic
|
|
103
|
+
if args.verbose:
|
|
104
|
+
print(f"📖 Loading schematic: {args.input}")
|
|
105
|
+
|
|
106
|
+
schematic = ksa.Schematic.load(args.input)
|
|
107
|
+
|
|
108
|
+
if args.verbose:
|
|
109
|
+
comp_count = len(list(schematic.components))
|
|
110
|
+
wire_count = len(list(schematic.wires))
|
|
111
|
+
label_count = len(list(schematic.labels))
|
|
112
|
+
print(f" Found {comp_count} components")
|
|
113
|
+
print(f" Found {wire_count} wires")
|
|
114
|
+
print(f" Found {label_count} labels")
|
|
115
|
+
|
|
116
|
+
# Generate Python code
|
|
117
|
+
if args.verbose:
|
|
118
|
+
print(f"🔨 Generating Python code...")
|
|
119
|
+
|
|
120
|
+
generator = PythonCodeGenerator(
|
|
121
|
+
template=args.template,
|
|
122
|
+
format_code=not args.no_format,
|
|
123
|
+
add_comments=not args.no_comments
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
code = generator.generate(
|
|
127
|
+
schematic=schematic,
|
|
128
|
+
include_hierarchy=args.include_hierarchy,
|
|
129
|
+
output_path=args.output
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Report success
|
|
133
|
+
lines = len(code.split('\n'))
|
|
134
|
+
print(f"✅ Generated {args.output} ({lines} lines)")
|
|
135
|
+
|
|
136
|
+
if args.verbose:
|
|
137
|
+
print(f" Template: {args.template}")
|
|
138
|
+
print(f" Formatted: {not args.no_format}")
|
|
139
|
+
print(f" Comments: {not args.no_comments}")
|
|
140
|
+
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
except FileNotFoundError as e:
|
|
144
|
+
print(f"❌ File not found: {e}", file=sys.stderr)
|
|
145
|
+
return 1
|
|
146
|
+
|
|
147
|
+
except CodeGenerationError as e:
|
|
148
|
+
print(f"❌ Code generation error: {e}", file=sys.stderr)
|
|
149
|
+
return 1
|
|
150
|
+
|
|
151
|
+
except TemplateNotFoundError as e:
|
|
152
|
+
print(f"❌ Template error: {e}", file=sys.stderr)
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(f"❌ Unexpected error: {e}", file=sys.stderr)
|
|
157
|
+
if args.verbose:
|
|
158
|
+
import traceback
|
|
159
|
+
traceback.print_exc()
|
|
160
|
+
return 1
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def entry_point():
|
|
164
|
+
"""Entry point for setuptools console_scripts."""
|
|
165
|
+
sys.exit(main())
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == '__main__':
|
|
169
|
+
sys.exit(main())
|