app
Adom Desktop - KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge — multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
"""kicad_export_molecule — one-shot "molecule export pack" for a KiCad board.
The Adom molecule pipeline (container side) is:
STEP → step2glb (color-preserving GLB) → adom-molecule create
--glb ... --pcb ... --sch ... --pro ... → Hydrogen molecule
(+ optionally a wiki.adom.inc component page)
adom-molecule REQUIRES four files for a KiCad source: .glb, .kicad_pcb,
.kicad_sch and .kicad_pro. The GLB comes from the STEP via step2glb *in the
container*; everything else lives here on the Windows host next to the board.
This verb does the entire desktop-side half in one call:
1. export the board's STEP (geometry + component models),
2. render silkscreen top/bottom PNGs when `kicad-cli pcb render` exists
(KiCad 9+; optional extras for the molecule page),
3. locate the sibling .kicad_sch / .kicad_pro by the board's stem,
4. return a single manifest of host paths + the exact container commands
to finish the pipeline.
The caller then pull_file's the manifest paths and runs the printed commands.
No container tools are shelled from here — the bridge stays desktop-only.
"""
import os
import subprocess
from pathlib import Path
from .export import _run_pcb_export
def handle_export_molecule(kicad_info: dict, args: dict) -> dict:
if not kicad_info.get("installed"):
return {
"success": False,
"error": "KiCad not installed",
"_hint": "Install KiCad first (kicad_upgrade), then retry.",
}
file_path = args.get("filePath", "")
if not file_path:
return {"success": False, "error": "No filePath specified (the .kicad_pcb)"}
pcb_path = Path(file_path)
if not pcb_path.exists():
return {
"success": False,
"error": f"PCB file not found: {file_path}",
"_hint": "Path must exist on the Windows host. send_files it first if it lives in Docker.",
}
out_dir = args.get("outputDir") or str(pcb_path.parent / "molecule_output")
os.makedirs(out_dir, exist_ok=True)
result: dict = {"success": True, "data": {}}
missing: list = []
# 1) STEP (reuse the standard export; honors its own error paths)
step_res = _run_pcb_export(
kicad_info,
{"filePath": str(pcb_path), "outputDir": out_dir},
"step",
"STEP",
)
if not step_res.get("success"):
return {
"success": False,
"error": f"STEP export failed: {step_res.get('error')}",
"_hint": "The STEP is mandatory for the molecule GLB — fix this first.",
}
step_path = step_res["data"]["output_path"]
result["data"]["step"] = step_path
# 2) Silkscreen renders (optional; needs `kicad-cli pcb render`, KiCad 9+)
cli_exe = kicad_info.get("kicad_cli_exe")
silk = {}
for side in ("top", "bottom"):
png = os.path.join(out_dir, f"{pcb_path.stem}-silk-{side}.png")
try:
r = subprocess.run(
[cli_exe, "pcb", "render", "--side", side,
"--background", "transparent", "--output", png, str(pcb_path)],
capture_output=True, text=True, timeout=180,
)
if r.returncode == 0 and Path(png).exists():
silk[side] = png
else:
missing.append(f"silk-{side} (kicad-cli pcb render rc={r.returncode})")
except Exception as e: # old KiCad without `pcb render`, timeout, …
missing.append(f"silk-{side} ({e})")
result["data"]["silkscreen"] = silk
# 3) Sibling project files by stem — adom-molecule needs sch + pro
sch = pcb_path.with_suffix(".kicad_sch")
pro = pcb_path.with_suffix(".kicad_pro")
result["data"]["pcb"] = str(pcb_path)
result["data"]["sch"] = str(sch) if sch.exists() else None
result["data"]["pro"] = str(pro) if pro.exists() else None
for label, p in (("sch", sch), ("pro", pro)):
if not p.exists():
missing.append(f"{label} ({p.name} not next to the board — adom-molecule requires it)")
# 4) The exact container-side finish
name = args.get("name") or pcb_path.stem
result["data"]["nextSteps"] = [
f"pull_file the paths in data (step/pcb/sch/pro/silkscreen) to the container",
f"step2glb convert <step> --out /tmp/{pcb_path.stem}.glb",
(
f"adom-molecule create --source kicad --name \"{name}\" --owner <owner> "
f"--description \"<desc>\" --glb /tmp/{pcb_path.stem}.glb "
f"--pcb <pcb> --sch <sch> --pro <pro>"
+ (" --silk-top <silk-top> --silk-bottom <silk-bottom>" if silk else "")
),
]
if missing:
result["data"]["missing"] = missing
result["output"] = (
f"Molecule export pack in {out_dir}: STEP"
+ (f" + silk({','.join(silk)})" if silk else "")
+ (f"; MISSING: {'; '.join(missing)}" if missing else " — complete")
)
result["_hint"] = (
"Desktop half of the molecule pipeline is done. pull_file everything in "
"data, then run data.nextSteps in the container (step2glb → adom-molecule "
"create). adom-molecule REQUIRES glb+pcb+sch+pro; silk PNGs are optional "
"polish for the molecule page."
)
return result