Wire native SES applier and headless DSN export into FreeRouting
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
Replace the two broken kicad-cli specctra subprocess calls: - import_ses_to_kicad now uses apply_ses_to_board (native, headless) instead of 'kicad-cli pcb import specctra-ses', logging segment/via/ net counts and warning on nets missing from the board. - export_dsn_from_kicad runs KiCad's bundled Python calling pcbnew.ExportSpecctraDSN (works headless, no wxApp) instead of 'kicad-cli pcb export specctra-dsn'. find_kicad_python locates a pcbnew-capable interpreter cross-platform: the current interpreter, then the macOS KiCad framework Python, then a python3 on PATH.
This commit is contained in:
parent
00fb40fbf4
commit
dbbb627278
@ -9,10 +9,12 @@ FreeRouting: https://www.freerouting.app/
|
||||
GitHub: https://github.com/freerouting/freerouting
|
||||
"""
|
||||
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Any
|
||||
@ -20,6 +22,7 @@ from typing import Any
|
||||
from kipy.board_types import BoardLayer
|
||||
|
||||
from .ipc_client import kicad_ipc_session
|
||||
from .ses_apply import apply_ses_to_board
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -29,6 +32,56 @@ class FreeRoutingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def find_kicad_python() -> str | None:
|
||||
"""Locate a Python interpreter that can ``import pcbnew``.
|
||||
|
||||
KiCad 10's ``kicad-cli`` has no Specctra subcommands, so DSN export runs
|
||||
through KiCad's bundled Python calling ``pcbnew.ExportSpecctraDSN`` (which
|
||||
works headless, unlike the GUI-only SES importer).
|
||||
|
||||
Order of preference:
|
||||
1. The current interpreter, if ``pcbnew`` is already importable.
|
||||
2. KiCad's bundled framework Python (macOS app bundle).
|
||||
3. A plain ``python3`` on PATH that can import ``pcbnew`` (Linux/Windows
|
||||
distro installs put ``pcbnew`` on the system Python's path).
|
||||
"""
|
||||
# 1. Current interpreter.
|
||||
try:
|
||||
import pcbnew # noqa: F401
|
||||
|
||||
return sys.executable
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
candidates: list[str] = []
|
||||
# 2. macOS bundled framework Python.
|
||||
candidates.extend(
|
||||
sorted(
|
||||
glob.glob(
|
||||
"/Applications/KiCad/KiCad.app/Contents/Frameworks/"
|
||||
"Python.framework/Versions/*/bin/python3"
|
||||
)
|
||||
)
|
||||
)
|
||||
# 3. Plain python3 on PATH.
|
||||
candidates.append("python3")
|
||||
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[candidate, "-c", "import pcbnew"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return candidate
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class FreeRoutingEngine:
|
||||
"""
|
||||
Engine for automated PCB routing using FreeRouting.
|
||||
@ -172,7 +225,11 @@ class FreeRoutingEngine:
|
||||
routing_options: dict[str, Any] | None = None
|
||||
) -> bool:
|
||||
"""
|
||||
Export DSN file from KiCad board using KiCad CLI.
|
||||
Export DSN file from KiCad board.
|
||||
|
||||
KiCad 10's ``kicad-cli`` has no ``specctra-dsn`` subcommand, so this
|
||||
invokes KiCad's bundled Python running ``pcbnew.ExportSpecctraDSN``,
|
||||
which works headless (no wxApp / display required).
|
||||
|
||||
Args:
|
||||
board_path: Path to .kicad_pcb file
|
||||
@ -182,19 +239,28 @@ class FreeRoutingEngine:
|
||||
Returns:
|
||||
True if export successful
|
||||
"""
|
||||
try:
|
||||
# Use KiCad CLI to export DSN
|
||||
cmd = [
|
||||
"kicad-cli", "pcb", "export", "specctra-dsn",
|
||||
"--output", dsn_output_path,
|
||||
board_path
|
||||
]
|
||||
python_exe = find_kicad_python()
|
||||
if python_exe is None:
|
||||
logger.error(
|
||||
"DSN export failed: no Python interpreter with 'pcbnew' found "
|
||||
"(install KiCad and ensure pcbnew is importable)"
|
||||
)
|
||||
return False
|
||||
|
||||
# Minimal headless export script: load the board, write the DSN.
|
||||
script = (
|
||||
"import sys, pcbnew\n"
|
||||
"board = pcbnew.LoadBoard(sys.argv[1])\n"
|
||||
"ok = pcbnew.ExportSpecctraDSN(board, sys.argv[2])\n"
|
||||
"sys.exit(0 if ok else 1)\n"
|
||||
)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
[python_exe, "-c", script, board_path, dsn_output_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
if result.returncode == 0 and os.path.isfile(dsn_output_path):
|
||||
@ -337,8 +403,13 @@ class FreeRoutingEngine:
|
||||
"""
|
||||
Import SES routing results back into KiCad board.
|
||||
|
||||
Uses the native, headless :func:`apply_ses_to_board` applier instead of
|
||||
``kicad-cli pcb import specctra-ses`` (which does not exist in KiCad 10)
|
||||
or ``pcbnew.ImportSpecctraSES`` (which requires a GUI display). Routed
|
||||
wires and vias are injected directly into the ``.kicad_pcb``.
|
||||
|
||||
Args:
|
||||
board_path: Path to .kicad_pcb file
|
||||
board_path: Path to .kicad_pcb file (updated in place)
|
||||
ses_path: Path to SES file with routing results
|
||||
backup_original: Whether to backup original board file
|
||||
|
||||
@ -353,30 +424,22 @@ class FreeRoutingEngine:
|
||||
shutil.copy2(board_path, backup_path)
|
||||
logger.info(f"Original board backed up to: {backup_path}")
|
||||
|
||||
# Use KiCad CLI to import SES file
|
||||
cmd = [
|
||||
"kicad-cli", "pcb", "import", "specctra-ses",
|
||||
"--output", board_path,
|
||||
ses_path
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
# Apply the SES natively (in place).
|
||||
result = apply_ses_to_board(board_path, ses_path, board_path)
|
||||
logger.info(
|
||||
"SES applied to %s: %d segments, %d vias across %d nets",
|
||||
board_path,
|
||||
result["segments_added"],
|
||||
result["vias_added"],
|
||||
result["nets_routed"],
|
||||
)
|
||||
if result["unknown_nets"]:
|
||||
logger.warning(
|
||||
"SES referenced nets not on the board (skipped): %s",
|
||||
", ".join(result["unknown_nets"]),
|
||||
)
|
||||
return True
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"SES imported successfully to: {board_path}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"SES import failed: {result.stderr}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("SES import timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error importing SES: {e}")
|
||||
return False
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user