fix(board-view): rebase onto main — fold responseMode + restore jpg
Rebased onto current main (d765bfe). Merged changes:
Board view (kicad-cli, cffi-free):
- Replace pcbnew/PLOT_CONTROLLER + cairosvg with kicad-cli SVG export
and cffi-free PNG conversion: pymupdf → inkscape → imagemagick chain
- pcbPath optional param with fallback to loaded board
- Restore jpg output via PIL post-processing on PNG bytes
- responseMode inline/file from main's #161: inline returns imageData,
file writes <board>_2d_view.<ext> and returns filePath
- shutil.which for cross-platform kicad-cli lookup
- Explicit TimeoutExpired catch; errorDetails on all error returns
- Validate fmt strictly; TypeScript returns image type for inline png/jpg
Schematic view (kicad_interface.py):
- Same cffi-free _svg_to_png helper for get_schematic_view and
get_schematic_view_region (pymupdf → inkscape → imagemagick)
Tests:
- Update test_get_board_2d_view_save_to_file.py to mock subprocess/
kicad-cli and _svg_to_png instead of PLOT_CONTROLLER/cairosvg
- All 5 responseMode tests pass
This commit is contained in:
@@ -271,6 +271,64 @@ except ImportError as e:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _svg_to_png(svg_path: str, width: int, height: int) -> Optional[bytes]:
|
||||
"""Convert SVG to PNG. No cffi dependency.
|
||||
|
||||
Priority:
|
||||
1. pymupdf (fitz) — bundled MuPDF renderer, pure Python, no system deps
|
||||
2. Inkscape CLI — accurate KiCAD SVG rendering
|
||||
3. ImageMagick convert — broad availability fallback
|
||||
Returns PNG bytes or None if all converters fail.
|
||||
"""
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
import fitz
|
||||
|
||||
doc = fitz.open(svg_path)
|
||||
page = doc[0]
|
||||
mat = fitz.Matrix(width / page.rect.width, height / page.rect.height)
|
||||
return page.get_pixmap(matrix=mat).tobytes("png")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
out_path = os.path.join(tempfile.mkdtemp(), "out.png")
|
||||
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[
|
||||
"inkscape",
|
||||
svg_path,
|
||||
"--export-type=png",
|
||||
f"--export-width={width}",
|
||||
f"--export-height={height}",
|
||||
f"--export-filename={out_path}",
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
if r.returncode == 0 and os.path.exists(out_path):
|
||||
with open(out_path, "rb") as f:
|
||||
return f.read()
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["convert", "-density", "150", svg_path, "-resize", f"{width}x{height}", out_path],
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
if r.returncode == 0 and os.path.exists(out_path):
|
||||
with open(out_path, "rb") as f:
|
||||
return f.read()
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class KiCADInterface:
|
||||
"""Main interface class to handle KiCAD operations"""
|
||||
|
||||
@@ -2898,22 +2956,18 @@ class KiCADInterface:
|
||||
svg_data = f.read()
|
||||
return {"success": True, "imageData": svg_data, "format": "svg"}
|
||||
|
||||
# Step 2: Convert SVG to PNG using cairosvg
|
||||
try:
|
||||
from cairosvg import svg2png
|
||||
except ImportError:
|
||||
# Fallback: return SVG data with a note
|
||||
# Step 2: Convert SVG to PNG (cffi-free)
|
||||
png_data = _svg_to_png(svg_path, width, height)
|
||||
if png_data is None:
|
||||
with open(svg_path, "r", encoding="utf-8") as f:
|
||||
svg_data = f.read()
|
||||
return {
|
||||
"success": True,
|
||||
"imageData": svg_data,
|
||||
"format": "svg",
|
||||
"message": "cairosvg not installed — returning SVG instead of PNG. Install with: pip install cairosvg",
|
||||
"message": "No PNG converter available — returning SVG. Install pymupdf, inkscape, or imagemagick.",
|
||||
}
|
||||
|
||||
png_data = svg2png(url=svg_path, output_width=width, output_height=height)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"imageData": base64.b64encode(png_data).decode("utf-8"),
|
||||
@@ -4871,16 +4925,12 @@ class KiCADInterface:
|
||||
svg_data = f.read()
|
||||
return {"success": True, "imageData": svg_data, "format": "svg"}
|
||||
else:
|
||||
try:
|
||||
from cairosvg import svg2png
|
||||
except ImportError:
|
||||
png_data = _svg_to_png(cropped_svg_path, width, height)
|
||||
if png_data is None:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "PNG export requires the 'cairosvg' package. Install it with: pip install cairosvg",
|
||||
"message": "No PNG converter available. Install pymupdf, inkscape, or imagemagick.",
|
||||
}
|
||||
png_data = svg2png(
|
||||
url=cropped_svg_path, output_width=width, output_height=height
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"imageData": base64.b64encode(png_data).decode("utf-8"),
|
||||
|
||||
Reference in New Issue
Block a user