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,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())