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
|
GitHub: https://github.com/freerouting/freerouting
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import glob
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -20,6 +22,7 @@ from typing import Any
|
|||||||
from kipy.board_types import BoardLayer
|
from kipy.board_types import BoardLayer
|
||||||
|
|
||||||
from .ipc_client import kicad_ipc_session
|
from .ipc_client import kicad_ipc_session
|
||||||
|
from .ses_apply import apply_ses_to_board
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -29,6 +32,56 @@ class FreeRoutingError(Exception):
|
|||||||
pass
|
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:
|
class FreeRoutingEngine:
|
||||||
"""
|
"""
|
||||||
Engine for automated PCB routing using FreeRouting.
|
Engine for automated PCB routing using FreeRouting.
|
||||||
@ -172,7 +225,11 @@ class FreeRoutingEngine:
|
|||||||
routing_options: dict[str, Any] | None = None
|
routing_options: dict[str, Any] | None = None
|
||||||
) -> bool:
|
) -> 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:
|
Args:
|
||||||
board_path: Path to .kicad_pcb file
|
board_path: Path to .kicad_pcb file
|
||||||
@ -182,19 +239,28 @@ class FreeRoutingEngine:
|
|||||||
Returns:
|
Returns:
|
||||||
True if export successful
|
True if export successful
|
||||||
"""
|
"""
|
||||||
try:
|
python_exe = find_kicad_python()
|
||||||
# Use KiCad CLI to export DSN
|
if python_exe is None:
|
||||||
cmd = [
|
logger.error(
|
||||||
"kicad-cli", "pcb", "export", "specctra-dsn",
|
"DSN export failed: no Python interpreter with 'pcbnew' found "
|
||||||
"--output", dsn_output_path,
|
"(install KiCad and ensure pcbnew is importable)"
|
||||||
board_path
|
)
|
||||||
]
|
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(
|
result = subprocess.run(
|
||||||
cmd,
|
[python_exe, "-c", script, board_path, dsn_output_path],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=60
|
timeout=120,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0 and os.path.isfile(dsn_output_path):
|
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.
|
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:
|
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
|
ses_path: Path to SES file with routing results
|
||||||
backup_original: Whether to backup original board file
|
backup_original: Whether to backup original board file
|
||||||
|
|
||||||
@ -353,30 +424,22 @@ class FreeRoutingEngine:
|
|||||||
shutil.copy2(board_path, backup_path)
|
shutil.copy2(board_path, backup_path)
|
||||||
logger.info(f"Original board backed up to: {backup_path}")
|
logger.info(f"Original board backed up to: {backup_path}")
|
||||||
|
|
||||||
# Use KiCad CLI to import SES file
|
# Apply the SES natively (in place).
|
||||||
cmd = [
|
result = apply_ses_to_board(board_path, ses_path, board_path)
|
||||||
"kicad-cli", "pcb", "import", "specctra-ses",
|
logger.info(
|
||||||
"--output", board_path,
|
"SES applied to %s: %d segments, %d vias across %d nets",
|
||||||
ses_path
|
board_path,
|
||||||
]
|
result["segments_added"],
|
||||||
|
result["vias_added"],
|
||||||
result = subprocess.run(
|
result["nets_routed"],
|
||||||
cmd,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=60
|
|
||||||
)
|
)
|
||||||
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error importing SES: {e}")
|
logger.error(f"Error importing SES: {e}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user