kicad-sch-api 0.3.5__py3-none-any.whl → 0.4.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 +2 -2
- 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/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +2 -2
- kicad_sch_api/collections/base.py +5 -7
- kicad_sch_api/collections/components.py +24 -12
- kicad_sch_api/collections/junctions.py +31 -43
- kicad_sch_api/collections/labels.py +19 -27
- kicad_sch_api/collections/wires.py +17 -18
- 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 +5 -0
- kicad_sch_api/core/components.py +67 -45
- kicad_sch_api/core/config.py +85 -3
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +276 -0
- kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +29 -53
- kicad_sch_api/core/managers/__init__.py +26 -0
- kicad_sch_api/core/managers/file_io.py +244 -0
- kicad_sch_api/core/managers/format_sync.py +501 -0
- kicad_sch_api/core/managers/graphics.py +579 -0
- kicad_sch_api/core/managers/metadata.py +269 -0
- kicad_sch_api/core/managers/sheet.py +454 -0
- kicad_sch_api/core/managers/text_elements.py +536 -0
- kicad_sch_api/core/managers/validation.py +475 -0
- kicad_sch_api/core/managers/wire.py +352 -0
- kicad_sch_api/core/nets.py +38 -43
- kicad_sch_api/core/no_connects.py +33 -55
- kicad_sch_api/core/parser.py +75 -1731
- kicad_sch_api/core/schematic.py +951 -1192
- kicad_sch_api/core/texts.py +28 -55
- kicad_sch_api/core/types.py +60 -22
- kicad_sch_api/core/wires.py +27 -75
- kicad_sch_api/geometry/font_metrics.py +3 -1
- kicad_sch_api/geometry/symbol_bbox.py +40 -21
- kicad_sch_api/interfaces/__init__.py +1 -1
- kicad_sch_api/interfaces/parser.py +1 -1
- kicad_sch_api/interfaces/repository.py +1 -1
- kicad_sch_api/interfaces/resolver.py +1 -1
- kicad_sch_api/parsers/__init__.py +2 -2
- kicad_sch_api/parsers/base.py +7 -10
- 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 +194 -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 +313 -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 +4 -2
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +1 -1
- kicad_sch_api/symbols/cache.py +9 -12
- kicad_sch_api/symbols/resolver.py +20 -26
- kicad_sch_api/symbols/validators.py +188 -137
- 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-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
- kicad_sch_api-0.4.1.dist-info/RECORD +87 -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/parsers/label_parser.py +0 -254
- kicad_sch_api/parsers/symbol_parser.py +0 -227
- kicad_sch_api/parsers/wire_parser.py +0 -99
- kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/top_level.txt +0 -0
kicad_sch_api/__init__.py
CHANGED
|
@@ -42,7 +42,7 @@ Advanced Usage:
|
|
|
42
42
|
print(f"Found {len(issues)} validation issues")
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
__version__ = "0.
|
|
45
|
+
__version__ = "0.4.0"
|
|
46
46
|
__author__ = "Circuit-Synth"
|
|
47
47
|
__email__ = "info@circuit-synth.com"
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ from .library.cache import SymbolLibraryCache, get_symbol_cache
|
|
|
55
55
|
from .utils.validation import ValidationError, ValidationIssue
|
|
56
56
|
|
|
57
57
|
# Version info
|
|
58
|
-
VERSION_INFO = (0,
|
|
58
|
+
VERSION_INFO = (0, 4, 0)
|
|
59
59
|
|
|
60
60
|
# Public API
|
|
61
61
|
__all__ = [
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KiCad CLI wrappers for schematic operations.
|
|
3
|
+
|
|
4
|
+
This module provides Python wrappers around kicad-cli commands with automatic
|
|
5
|
+
fallback to Docker when kicad-cli is not installed locally.
|
|
6
|
+
|
|
7
|
+
Supported operations:
|
|
8
|
+
- ERC (Electrical Rule Check)
|
|
9
|
+
- Netlist export (8 formats)
|
|
10
|
+
- BOM (Bill of Materials) export
|
|
11
|
+
- PDF export
|
|
12
|
+
- SVG export
|
|
13
|
+
- DXF export
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> import kicad_sch_api as ksa
|
|
17
|
+
>>> sch = ksa.Schematic('circuit.kicad_sch')
|
|
18
|
+
>>> sch.run_erc()
|
|
19
|
+
>>> sch.export_netlist(format='spice')
|
|
20
|
+
>>> sch.export_bom(exclude_dnp=True)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from kicad_sch_api.cli.base import (
|
|
24
|
+
ExecutionMode,
|
|
25
|
+
KiCadExecutor,
|
|
26
|
+
get_executor_info,
|
|
27
|
+
set_execution_mode,
|
|
28
|
+
)
|
|
29
|
+
from kicad_sch_api.cli.types import (
|
|
30
|
+
ErcFormat,
|
|
31
|
+
ErcSeverity,
|
|
32
|
+
NetlistFormat,
|
|
33
|
+
Units,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"KiCadExecutor",
|
|
38
|
+
"ExecutionMode",
|
|
39
|
+
"get_executor_info",
|
|
40
|
+
"set_execution_mode",
|
|
41
|
+
"NetlistFormat",
|
|
42
|
+
"ErcFormat",
|
|
43
|
+
"ErcSeverity",
|
|
44
|
+
"Units",
|
|
45
|
+
]
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base KiCad CLI executor with Docker fallback support.
|
|
3
|
+
|
|
4
|
+
This module provides the core infrastructure for executing kicad-cli commands
|
|
5
|
+
either locally or via Docker containers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
from kicad_sch_api.cli.types import ExecutionMode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ExecutorInfo:
|
|
20
|
+
"""Information about available execution modes."""
|
|
21
|
+
local_available: bool
|
|
22
|
+
local_version: Optional[str]
|
|
23
|
+
docker_available: bool
|
|
24
|
+
docker_image: str
|
|
25
|
+
active_mode: ExecutionMode
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class KiCadExecutor:
|
|
29
|
+
"""
|
|
30
|
+
Core executor for KiCad CLI commands with Docker fallback.
|
|
31
|
+
|
|
32
|
+
Execution strategy:
|
|
33
|
+
1. AUTO mode (default): Try local kicad-cli, fall back to Docker
|
|
34
|
+
2. LOCAL mode: Force local kicad-cli (fail if not available)
|
|
35
|
+
3. DOCKER mode: Force Docker (fail if Docker not available)
|
|
36
|
+
|
|
37
|
+
Environment variables:
|
|
38
|
+
- KICAD_CLI_MODE: Set execution mode (auto|local|docker)
|
|
39
|
+
- KICAD_DOCKER_IMAGE: Override Docker image (default: kicad/kicad:latest)
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> executor = KiCadExecutor()
|
|
43
|
+
>>> result = executor.run(['sch', 'export', 'netlist', 'circuit.kicad_sch'])
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Class-level cache for detection results
|
|
47
|
+
_local_available: Optional[bool] = None
|
|
48
|
+
_local_version: Optional[str] = None
|
|
49
|
+
_docker_available: Optional[bool] = None
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
mode: ExecutionMode = "auto",
|
|
54
|
+
docker_image: str = "kicad/kicad:latest",
|
|
55
|
+
verbose: bool = False,
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Initialize KiCad executor.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
mode: Execution mode (auto, local, or docker)
|
|
62
|
+
docker_image: Docker image to use
|
|
63
|
+
verbose: Print execution details
|
|
64
|
+
"""
|
|
65
|
+
# Check environment variable override
|
|
66
|
+
env_mode = os.getenv("KICAD_CLI_MODE", "").lower()
|
|
67
|
+
if env_mode in ("auto", "local", "docker"):
|
|
68
|
+
mode = env_mode # type: ignore
|
|
69
|
+
|
|
70
|
+
env_image = os.getenv("KICAD_DOCKER_IMAGE", "")
|
|
71
|
+
if env_image:
|
|
72
|
+
docker_image = env_image
|
|
73
|
+
|
|
74
|
+
self.mode = mode
|
|
75
|
+
self.docker_image = docker_image
|
|
76
|
+
self.verbose = verbose
|
|
77
|
+
|
|
78
|
+
# Detect capabilities on first use
|
|
79
|
+
if KiCadExecutor._local_available is None:
|
|
80
|
+
KiCadExecutor._detect_local()
|
|
81
|
+
if KiCadExecutor._docker_available is None:
|
|
82
|
+
KiCadExecutor._detect_docker()
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def _detect_local(cls) -> None:
|
|
86
|
+
"""Detect if kicad-cli is available locally."""
|
|
87
|
+
try:
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
["kicad-cli", "version"],
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
timeout=5,
|
|
93
|
+
)
|
|
94
|
+
if result.returncode == 0:
|
|
95
|
+
cls._local_available = True
|
|
96
|
+
cls._local_version = result.stdout.strip()
|
|
97
|
+
else:
|
|
98
|
+
cls._local_available = False
|
|
99
|
+
cls._local_version = None
|
|
100
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
101
|
+
cls._local_available = False
|
|
102
|
+
cls._local_version = None
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def _detect_docker(cls) -> None:
|
|
106
|
+
"""Detect if Docker is available."""
|
|
107
|
+
try:
|
|
108
|
+
result = subprocess.run(
|
|
109
|
+
["docker", "version"],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
timeout=5,
|
|
112
|
+
)
|
|
113
|
+
cls._docker_available = result.returncode == 0
|
|
114
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
115
|
+
cls._docker_available = False
|
|
116
|
+
|
|
117
|
+
def _run_local(self, args: List[str], cwd: Optional[Path] = None) -> subprocess.CompletedProcess:
|
|
118
|
+
"""Run kicad-cli locally."""
|
|
119
|
+
if not KiCadExecutor._local_available:
|
|
120
|
+
raise RuntimeError(
|
|
121
|
+
"kicad-cli not found. Install KiCad or use Docker mode.\n"
|
|
122
|
+
"Install: https://www.kicad.org/download/\n"
|
|
123
|
+
"Docker: export KICAD_CLI_MODE=docker"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
cmd = ["kicad-cli"] + args
|
|
127
|
+
if self.verbose:
|
|
128
|
+
print(f"Running locally: {' '.join(cmd)}")
|
|
129
|
+
|
|
130
|
+
result = subprocess.run(
|
|
131
|
+
cmd,
|
|
132
|
+
capture_output=True,
|
|
133
|
+
text=True,
|
|
134
|
+
cwd=cwd,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
def _run_docker(self, args: List[str], cwd: Optional[Path] = None) -> subprocess.CompletedProcess:
|
|
140
|
+
"""Run kicad-cli via Docker."""
|
|
141
|
+
if not KiCadExecutor._docker_available:
|
|
142
|
+
raise RuntimeError(
|
|
143
|
+
"Docker not found. Install Docker or use local mode.\n"
|
|
144
|
+
"Install: https://docs.docker.com/get-docker/\n"
|
|
145
|
+
"Local: Install KiCad from https://www.kicad.org/download/"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Determine working directory
|
|
149
|
+
if cwd:
|
|
150
|
+
work_dir = cwd.resolve()
|
|
151
|
+
else:
|
|
152
|
+
work_dir = Path.cwd()
|
|
153
|
+
|
|
154
|
+
# Build Docker command
|
|
155
|
+
docker_cmd = [
|
|
156
|
+
"docker", "run",
|
|
157
|
+
"--rm", # Remove container after execution
|
|
158
|
+
"-v", f"{work_dir}:/workspace", # Mount working directory
|
|
159
|
+
"-w", "/workspace", # Set working directory
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Add user mapping on Linux/Mac to avoid permission issues
|
|
163
|
+
if os.name != "nt": # Not Windows
|
|
164
|
+
uid = os.getuid()
|
|
165
|
+
gid = os.getgid()
|
|
166
|
+
docker_cmd.extend(["--user", f"{uid}:{gid}"])
|
|
167
|
+
|
|
168
|
+
docker_cmd.extend([
|
|
169
|
+
self.docker_image,
|
|
170
|
+
"kicad-cli",
|
|
171
|
+
] + args)
|
|
172
|
+
|
|
173
|
+
if self.verbose:
|
|
174
|
+
print(f"Running via Docker: {' '.join(docker_cmd)}")
|
|
175
|
+
|
|
176
|
+
# Check if image exists, pull if needed
|
|
177
|
+
self._ensure_docker_image()
|
|
178
|
+
|
|
179
|
+
result = subprocess.run(
|
|
180
|
+
docker_cmd,
|
|
181
|
+
capture_output=True,
|
|
182
|
+
text=True,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
def _ensure_docker_image(self) -> None:
|
|
188
|
+
"""Ensure Docker image is available, pull if needed."""
|
|
189
|
+
# Check if image exists
|
|
190
|
+
result = subprocess.run(
|
|
191
|
+
["docker", "image", "inspect", self.docker_image],
|
|
192
|
+
capture_output=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if result.returncode != 0:
|
|
196
|
+
# Image not found, pull it
|
|
197
|
+
if self.verbose:
|
|
198
|
+
print(f"Pulling Docker image: {self.docker_image}")
|
|
199
|
+
|
|
200
|
+
pull_result = subprocess.run(
|
|
201
|
+
["docker", "pull", self.docker_image],
|
|
202
|
+
capture_output=not self.verbose,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if pull_result.returncode != 0:
|
|
206
|
+
raise RuntimeError(f"Failed to pull Docker image: {self.docker_image}")
|
|
207
|
+
|
|
208
|
+
def run(
|
|
209
|
+
self,
|
|
210
|
+
args: List[str],
|
|
211
|
+
cwd: Optional[Path] = None,
|
|
212
|
+
check: bool = True,
|
|
213
|
+
) -> subprocess.CompletedProcess:
|
|
214
|
+
"""
|
|
215
|
+
Execute kicad-cli command.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
args: Command arguments (e.g., ['sch', 'export', 'netlist', 'file.kicad_sch'])
|
|
219
|
+
cwd: Working directory (default: current directory)
|
|
220
|
+
check: Raise exception on non-zero exit code
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
CompletedProcess with stdout, stderr, returncode
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
RuntimeError: If execution fails or kicad-cli unavailable
|
|
227
|
+
"""
|
|
228
|
+
if self.mode == "local":
|
|
229
|
+
result = self._run_local(args, cwd)
|
|
230
|
+
elif self.mode == "docker":
|
|
231
|
+
result = self._run_docker(args, cwd)
|
|
232
|
+
else: # auto mode
|
|
233
|
+
if KiCadExecutor._local_available:
|
|
234
|
+
if self.verbose:
|
|
235
|
+
print(f"Using local KiCad CLI ({KiCadExecutor._local_version})")
|
|
236
|
+
result = self._run_local(args, cwd)
|
|
237
|
+
elif KiCadExecutor._docker_available:
|
|
238
|
+
if self.verbose:
|
|
239
|
+
print("Local KiCad CLI not found, using Docker")
|
|
240
|
+
result = self._run_docker(args, cwd)
|
|
241
|
+
else:
|
|
242
|
+
raise RuntimeError(
|
|
243
|
+
"KiCad CLI not available in any mode.\n\n"
|
|
244
|
+
"Install options:\n"
|
|
245
|
+
"1. Install KiCad: https://www.kicad.org/download/\n"
|
|
246
|
+
"2. Install Docker: https://docs.docker.com/get-docker/"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if check and result.returncode != 0:
|
|
250
|
+
error_msg = result.stderr if result.stderr else result.stdout
|
|
251
|
+
raise RuntimeError(f"KiCad CLI command failed:\n{error_msg}")
|
|
252
|
+
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def get_info(cls) -> ExecutorInfo:
|
|
257
|
+
"""Get information about available execution modes."""
|
|
258
|
+
if cls._local_available is None:
|
|
259
|
+
cls._detect_local()
|
|
260
|
+
if cls._docker_available is None:
|
|
261
|
+
cls._detect_docker()
|
|
262
|
+
|
|
263
|
+
# Determine active mode
|
|
264
|
+
env_mode = os.getenv("KICAD_CLI_MODE", "auto").lower()
|
|
265
|
+
if env_mode not in ("auto", "local", "docker"):
|
|
266
|
+
env_mode = "auto"
|
|
267
|
+
|
|
268
|
+
return ExecutorInfo(
|
|
269
|
+
local_available=cls._local_available or False,
|
|
270
|
+
local_version=cls._local_version,
|
|
271
|
+
docker_available=cls._docker_available or False,
|
|
272
|
+
docker_image=os.getenv("KICAD_DOCKER_IMAGE", "kicad/kicad:latest"),
|
|
273
|
+
active_mode=env_mode, # type: ignore
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# Convenience functions
|
|
278
|
+
def get_executor_info() -> ExecutorInfo:
|
|
279
|
+
"""
|
|
280
|
+
Get information about KiCad CLI availability.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
ExecutorInfo with details about local and Docker availability
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> info = get_executor_info()
|
|
287
|
+
>>> print(f"Local: {info.local_available}, Docker: {info.docker_available}")
|
|
288
|
+
"""
|
|
289
|
+
return KiCadExecutor.get_info()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def set_execution_mode(mode: ExecutionMode) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Set the execution mode for all KiCad CLI operations.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
mode: Execution mode (auto, local, or docker)
|
|
298
|
+
|
|
299
|
+
Example:
|
|
300
|
+
>>> set_execution_mode('docker') # Force Docker mode
|
|
301
|
+
"""
|
|
302
|
+
os.environ["KICAD_CLI_MODE"] = mode
|
kicad_sch_api/cli/bom.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Bill of Materials (BOM) export functionality using kicad-cli."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from kicad_sch_api.cli.base import KiCadExecutor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def export_bom(
|
|
10
|
+
schematic_path: Path,
|
|
11
|
+
output_path: Optional[Path] = None,
|
|
12
|
+
preset: Optional[str] = None,
|
|
13
|
+
format_preset: Optional[str] = None,
|
|
14
|
+
fields: Optional[List[str]] = None,
|
|
15
|
+
labels: Optional[List[str]] = None,
|
|
16
|
+
group_by: Optional[List[str]] = None,
|
|
17
|
+
sort_field: str = "Reference",
|
|
18
|
+
sort_asc: bool = True,
|
|
19
|
+
filter: Optional[str] = None,
|
|
20
|
+
exclude_dnp: bool = False,
|
|
21
|
+
include_excluded_from_bom: bool = False,
|
|
22
|
+
field_delimiter: str = ",",
|
|
23
|
+
string_delimiter: str = '"',
|
|
24
|
+
ref_delimiter: str = ",",
|
|
25
|
+
ref_range_delimiter: str = "",
|
|
26
|
+
keep_tabs: bool = False,
|
|
27
|
+
keep_line_breaks: bool = False,
|
|
28
|
+
executor: Optional[KiCadExecutor] = None,
|
|
29
|
+
) -> Path:
|
|
30
|
+
"""
|
|
31
|
+
Export Bill of Materials (BOM) from schematic using kicad-cli.
|
|
32
|
+
|
|
33
|
+
Generates a CSV file with component information, suitable for manufacturing
|
|
34
|
+
and procurement.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
schematic_path: Path to .kicad_sch file
|
|
38
|
+
output_path: Output BOM path (auto-generated if None)
|
|
39
|
+
preset: Named BOM preset from schematic (e.g., "Grouped By Value")
|
|
40
|
+
format_preset: Named BOM format preset (e.g., "CSV")
|
|
41
|
+
fields: List of fields to export (default: Reference, Value, Footprint, Qty, DNP)
|
|
42
|
+
labels: List of labels for exported fields
|
|
43
|
+
group_by: Fields to group references by when values match
|
|
44
|
+
sort_field: Field name to sort by (default: "Reference")
|
|
45
|
+
sort_asc: Sort ascending (True) or descending (False)
|
|
46
|
+
filter: Filter string to remove output lines
|
|
47
|
+
exclude_dnp: Exclude components marked Do-Not-Populate
|
|
48
|
+
include_excluded_from_bom: Include components marked 'Exclude from BOM'
|
|
49
|
+
field_delimiter: Separator between fields/columns (default: ",")
|
|
50
|
+
string_delimiter: Character to surround fields with (default: '"')
|
|
51
|
+
ref_delimiter: Character between individual references (default: ",")
|
|
52
|
+
ref_range_delimiter: Character for reference ranges (default: "", use "-" for ranges like "R1-R5")
|
|
53
|
+
keep_tabs: Keep tab characters from input fields
|
|
54
|
+
keep_line_breaks: Keep line break characters from input fields
|
|
55
|
+
executor: Custom KiCadExecutor instance (creates default if None)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Path to generated BOM file
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
RuntimeError: If kicad-cli not found or BOM generation fails
|
|
62
|
+
FileNotFoundError: If schematic file doesn't exist
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> from pathlib import Path
|
|
66
|
+
>>> bom = export_bom(
|
|
67
|
+
... Path('circuit.kicad_sch'),
|
|
68
|
+
... fields=['Reference', 'Value', 'Footprint', 'MPN'],
|
|
69
|
+
... group_by=['Value', 'Footprint'],
|
|
70
|
+
... exclude_dnp=True,
|
|
71
|
+
... )
|
|
72
|
+
>>> print(f"BOM: {bom}")
|
|
73
|
+
|
|
74
|
+
Common use cases:
|
|
75
|
+
# Simple BOM with default fields
|
|
76
|
+
>>> export_bom(Path('circuit.kicad_sch'))
|
|
77
|
+
|
|
78
|
+
# Manufacturing BOM (exclude DNP, group by value)
|
|
79
|
+
>>> export_bom(
|
|
80
|
+
... Path('circuit.kicad_sch'),
|
|
81
|
+
... group_by=['Value', 'Footprint'],
|
|
82
|
+
... exclude_dnp=True,
|
|
83
|
+
... )
|
|
84
|
+
|
|
85
|
+
# BOM with manufacturer part numbers
|
|
86
|
+
>>> export_bom(
|
|
87
|
+
... Path('circuit.kicad_sch'),
|
|
88
|
+
... fields=['Reference', 'Value', 'Footprint', 'MPN', 'Manufacturer'],
|
|
89
|
+
... group_by=['MPN'],
|
|
90
|
+
... )
|
|
91
|
+
"""
|
|
92
|
+
schematic_path = Path(schematic_path)
|
|
93
|
+
|
|
94
|
+
if not schematic_path.exists():
|
|
95
|
+
raise FileNotFoundError(f"Schematic not found: {schematic_path}")
|
|
96
|
+
|
|
97
|
+
# Auto-generate output path if not provided
|
|
98
|
+
if output_path is None:
|
|
99
|
+
output_path = schematic_path.with_suffix(".csv")
|
|
100
|
+
else:
|
|
101
|
+
output_path = Path(output_path)
|
|
102
|
+
|
|
103
|
+
# Create executor if not provided
|
|
104
|
+
if executor is None:
|
|
105
|
+
executor = KiCadExecutor()
|
|
106
|
+
|
|
107
|
+
# Build command
|
|
108
|
+
args = [
|
|
109
|
+
"sch", "export", "bom",
|
|
110
|
+
"--output", str(output_path),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
# Add optional parameters
|
|
114
|
+
if preset:
|
|
115
|
+
args.extend(["--preset", preset])
|
|
116
|
+
|
|
117
|
+
if format_preset:
|
|
118
|
+
args.extend(["--format-preset", format_preset])
|
|
119
|
+
|
|
120
|
+
if fields:
|
|
121
|
+
args.extend(["--fields", ",".join(fields)])
|
|
122
|
+
|
|
123
|
+
if labels:
|
|
124
|
+
args.extend(["--labels", ",".join(labels)])
|
|
125
|
+
|
|
126
|
+
if group_by:
|
|
127
|
+
args.extend(["--group-by", ",".join(group_by)])
|
|
128
|
+
|
|
129
|
+
if sort_field:
|
|
130
|
+
args.extend(["--sort-field", sort_field])
|
|
131
|
+
|
|
132
|
+
if sort_asc:
|
|
133
|
+
args.append("--sort-asc")
|
|
134
|
+
|
|
135
|
+
if filter:
|
|
136
|
+
args.extend(["--filter", filter])
|
|
137
|
+
|
|
138
|
+
if exclude_dnp:
|
|
139
|
+
args.append("--exclude-dnp")
|
|
140
|
+
|
|
141
|
+
if include_excluded_from_bom:
|
|
142
|
+
args.append("--include-excluded-from-bom")
|
|
143
|
+
|
|
144
|
+
# Delimiter options
|
|
145
|
+
args.extend(["--field-delimiter", field_delimiter])
|
|
146
|
+
args.extend(["--string-delimiter", string_delimiter])
|
|
147
|
+
args.extend(["--ref-delimiter", ref_delimiter])
|
|
148
|
+
|
|
149
|
+
if ref_range_delimiter:
|
|
150
|
+
args.extend(["--ref-range-delimiter", ref_range_delimiter])
|
|
151
|
+
|
|
152
|
+
if keep_tabs:
|
|
153
|
+
args.append("--keep-tabs")
|
|
154
|
+
|
|
155
|
+
if keep_line_breaks:
|
|
156
|
+
args.append("--keep-line-breaks")
|
|
157
|
+
|
|
158
|
+
# Add schematic path
|
|
159
|
+
args.append(str(schematic_path))
|
|
160
|
+
|
|
161
|
+
# Execute command
|
|
162
|
+
executor.run(args, cwd=schematic_path.parent)
|
|
163
|
+
|
|
164
|
+
return output_path
|