Skip to content

Commit 9b88b85

Browse files
committed
feat(motor test): convert motor test diagrams from SVG into PNG
Required because TKinter can not display .svg files
1 parent 80bc4cf commit 9b88b85

1 file changed

Lines changed: 180 additions & 1 deletion

File tree

scripts/download_motor_diagrams.py

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,68 @@
1111
SPDX-License-Identifier: GPL-3.0-or-later
1212
"""
1313

14+
import subprocess
1415
import urllib.request
1516
from pathlib import Path
17+
from typing import Optional
1618
from urllib.parse import urlparse
1719

20+
# Modern SVG conversion backends (in order of preference)
21+
try:
22+
import cairosvg
23+
24+
HAS_CAIROSVG = True
25+
except ImportError:
26+
HAS_CAIROSVG = False
27+
28+
try:
29+
from wand import image as wand_image
30+
31+
HAS_WAND = True
32+
except ImportError:
33+
HAS_WAND = False
34+
35+
try:
36+
from reportlab.graphics import renderPDF, renderPM
37+
from svglib.svglib import renderSVG
38+
39+
HAS_SVGLIB = True
40+
except ImportError:
41+
HAS_SVGLIB = False
42+
43+
44+
# Check for system tools
45+
def has_inkscape() -> bool:
46+
"""Check if Inkscape is available."""
47+
try:
48+
subprocess.run(["inkscape", "--version"], capture_output=True, check=True)
49+
return True
50+
except (subprocess.CalledProcessError, FileNotFoundError):
51+
return False
52+
53+
54+
def get_inkscape_version() -> tuple[int, int]:
55+
"""Get Inkscape major and minor version numbers."""
56+
try:
57+
result = subprocess.run(["inkscape", "--version"], capture_output=True, check=True, text=True)
58+
# Parse version like "Inkscape 1.4.2 (2aeb623e1d, 2025-05-12)"
59+
version_line = result.stdout.strip().split("\n")[0]
60+
version_part = version_line.split()[1] # Get "1.4.2"
61+
major, minor = map(int, version_part.split(".")[:2]) # Get 1, 4
62+
return major, minor
63+
except (subprocess.CalledProcessError, FileNotFoundError, ValueError, IndexError):
64+
return 0, 92 # Default to old version format
65+
66+
67+
def has_rsvg_convert() -> bool:
68+
"""Check if rsvg-convert is available."""
69+
try:
70+
subprocess.run(["rsvg-convert", "--version"], capture_output=True, check=True)
71+
return True
72+
except (subprocess.CalledProcessError, FileNotFoundError):
73+
return False
74+
75+
1876
# ruff: noqa: T201
1977

2078
# List of all motor diagram SVG files from the ArduPilot documentation at
@@ -109,5 +167,126 @@ def download_motor_diagrams() -> None:
109167
print(f"\nDownload complete: {downloaded} succeeded, {failed} failed")
110168

111169

170+
def convert_svg_to_png(result_height: Optional[int] = None) -> None:
171+
"""
172+
Convert all downloaded SVG files to PNG using the best available backend.
173+
174+
Args:
175+
result_height: Optional target height in pixels. Width will be calculated
176+
to preserve aspect ratio. If None, uses original size.
177+
178+
Tries conversion backends in order of preference:
179+
1. Inkscape (most reliable, high quality)
180+
2. rsvg-convert (librsvg, good quality)
181+
3. Wand/ImageMagick (good for complex SVGs)
182+
4. svglib + reportlab (pure Python, fast)
183+
5. cairosvg (fallback, sometimes has issues)
184+
185+
"""
186+
images_dir = Path("ardupilot_methodic_configurator/images")
187+
188+
# Determine the best available backend
189+
backend = None
190+
if has_inkscape():
191+
backend = "inkscape"
192+
print("Using Inkscape for SVG conversion (best quality)")
193+
elif has_rsvg_convert():
194+
backend = "rsvg-convert"
195+
print("Using rsvg-convert for SVG conversion (good quality)")
196+
elif HAS_WAND:
197+
backend = "wand"
198+
print("Using Wand/ImageMagick for SVG conversion")
199+
elif HAS_SVGLIB:
200+
backend = "svglib"
201+
print("Using svglib for SVG conversion")
202+
elif HAS_CAIROSVG:
203+
backend = "cairosvg"
204+
print("Using cairosvg for SVG conversion (fallback)")
205+
else:
206+
print("No SVG conversion backend available. Install one of:")
207+
print(" - inkscape (apt install inkscape)")
208+
print(" - librsvg2-bin (apt install librsvg2-bin)")
209+
print(" - pip install Wand")
210+
print(" - pip install svglib reportlab")
211+
print(" - pip install cairosvg")
212+
return
213+
214+
converted = 0
215+
failed = 0
216+
217+
for filename in motor_diagrams:
218+
try:
219+
svg_path = images_dir / filename
220+
png_path = images_dir / filename.replace(".svg", ".png")
221+
222+
if not svg_path.exists():
223+
print(f"SVG file not found: {svg_path}")
224+
failed += 1
225+
continue
226+
227+
print(f"Converting {filename} to PNG...")
228+
229+
if backend == "inkscape":
230+
# Use different syntax based on Inkscape version
231+
major, minor = get_inkscape_version()
232+
233+
if major >= 1: # Modern Inkscape (1.0+) with modern syntax
234+
cmd = ["inkscape", "--export-type=png", f"--export-filename={png_path}", str(svg_path)]
235+
if result_height is not None:
236+
cmd.insert(-1, f"--export-height={result_height}")
237+
else: # Legacy Inkscape (0.x) with old syntax
238+
cmd = ["inkscape", str(svg_path), "--export-png", str(png_path), "--export-dpi=300"]
239+
if result_height is not None:
240+
cmd.append(f"--export-height={result_height}")
241+
242+
subprocess.run(cmd, check=True, capture_output=True)
243+
244+
elif backend == "rsvg-convert":
245+
cmd = [
246+
"rsvg-convert",
247+
"-f",
248+
"png",
249+
"-d",
250+
"600", # DPI
251+
"-p",
252+
"600", # DPI
253+
]
254+
255+
# Add height parameter if specified
256+
if result_height is not None:
257+
cmd.extend(["-h", str(result_height)])
258+
259+
cmd.append(str(svg_path))
260+
261+
with open(png_path, "wb") as f:
262+
subprocess.run(cmd, stdout=f, check=True)
263+
264+
elif backend == "wand":
265+
with wand_image.Image() as img:
266+
img.read(filename=str(svg_path))
267+
img.format = "png"
268+
img.resolution = (300, 300)
269+
img.save(filename=str(png_path))
270+
271+
elif backend == "svglib":
272+
from reportlab.graphics import renderPM
273+
from svglib.svglib import renderSVG
274+
275+
drawing = renderSVG.renderSVG(str(svg_path))
276+
renderPM.drawToFile(drawing, str(png_path), fmt="PNG", dpi=300)
277+
278+
elif backend == "cairosvg":
279+
cairosvg.svg2png(url=str(svg_path), write_to=str(png_path), dpi=300)
280+
281+
converted += 1
282+
283+
except Exception as e: # pylint: disable=broad-exception-caught
284+
print(f"Failed to convert {filename}: {e}")
285+
failed += 1
286+
287+
print(f"\nConversion complete: {converted} succeeded, {failed} failed")
288+
289+
112290
if __name__ == "__main__":
113-
download_motor_diagrams()
291+
# download_motor_diagrams()
292+
convert_svg_to_png(result_height=320)

0 commit comments

Comments
 (0)