diff --git a/kibot/PcbDraw/convert.py b/kibot/PcbDraw/convert.py deleted file mode 100644 index 7fe64738..00000000 --- a/kibot/PcbDraw/convert.py +++ /dev/null @@ -1,96 +0,0 @@ -# Author: Jan Mrázek -# License: MIT -import platform -import subprocess -import textwrap -import os -from typing import Union -from tempfile import TemporaryDirectory -# from PIL import Image -from lxml.etree import _ElementTree # type: ignore - -# Converting SVG to bitmap is a hard problem. We used Wand (and thus -# imagemagick) to do the conversion. However, imagemagick is really hard to -# configure properly and it breaks often. Therefore, we provide a custom module -# that has several conversion strategies that reflect the platform. We also try -# to provide descriptive enough message so the user can detect what is wrong. - -if platform.system() == "Windows": - from .convert_windows import detectInkscape -else: - from .convert_unix import detectInkscape, rsvgSvgToPng - -def inkscapeSvgToPng(inputFilename: str, outputFilename: str, dpi: int) -> None: - """ - A strategy to convert an SVG file into a PNG file using Inkscape - """ - command = [detectInkscape(), "--export-type=png", f"--export-dpi={dpi}", - f"--export-filename={outputFilename}", inputFilename] - def reportError(message: str) -> None: - raise RuntimeError(f"Cannot convert {inputFilename} to {outputFilename}. Inkscape failed with:\n" - + textwrap.indent(message, " ")) - try: - r = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output = r.stdout.decode("utf-8") + "\n" + r.stderr.decode("utf-8") - # Inkscape doesn't respect error codes - if "Can't open file" in output: - reportError(output) - except subprocess.CalledProcessError as e: - output = e.stdout.decode("utf-8") + "\n" + e.stderr.decode("utf-8") - reportError(output) - -def svgToPng(inputFilename: str, outputFilename: str, dpi: int=300) -> None: - """ - Convert SVG file into a PNG file based on platform-dependent strategies - """ - if platform.system() == "Windows": - strategies = { - "Inkscape": inkscapeSvgToPng - } - else: - strategies = { - "RSVG": rsvgSvgToPng, # We prefer it over Inkscape as it is much faster - "Inkscape": inkscapeSvgToPng - } - - errors = {} - for name, strategy in strategies.items(): - try: - strategy(inputFilename, outputFilename, dpi) - return - except Exception as e: - errors[name] = str(e) - message = "Cannot convert PNG to SVG; all strategies failed:\n" - for name, error in errors.items(): - m = f"- Strategy '{name}' failed with: {textwrap.indent(error, ' ')}\n" - message += textwrap.indent(m, " ") - raise RuntimeError(message) - -def save(image: _ElementTree, filename: str, dpi: int=600, format: str=None) -> None: - """ - Given an SVG tree or an image, save to a filename. The format is deduced - from the extension. - """ - ftype = os.path.splitext(filename)[1][1:].lower() if format is None else format -# if isinstance(image, Image.Image): -# if ftype not in ["jpg", "jpeg", "png", "bmp"]: -# raise TypeError(f"Cannot save bitmap image into {ftype}") -# image.save(filename) -# return - if isinstance(image, _ElementTree): - if ftype == "svg": - image.write(filename) - return - with TemporaryDirectory() as d: - svg_filename = os.path.join(d, "image.svg") - if ftype == "png": - png_filename = filename - else: - png_filename = os.path.join(d, "image.png") - image.write(svg_filename) - svgToPng(svg_filename, png_filename, dpi=dpi) - if ftype == "png": - return -# Image.open(png_filename).convert("RGB").save(filename) -# return - raise TypeError(f"Unknown image type: {type(image)}") diff --git a/kibot/PcbDraw/convert_common.py b/kibot/PcbDraw/convert_common.py deleted file mode 100644 index 253275c8..00000000 --- a/kibot/PcbDraw/convert_common.py +++ /dev/null @@ -1,29 +0,0 @@ -# Author: Jan Mrázek -# License: MIT -import subprocess -from typing import List - -def isValidInkscape(executable: str) -> bool: - try: - out = subprocess.check_output([executable, "--version"]).decode("utf-8") - parts = out.split(" ") - if parts[0] != "Inkscape": - return False - version = parts[1].split(".") - return int(version[0]) == 1 - except FileNotFoundError: - return False - except subprocess.CalledProcessError as e: - return False - -def chooseInkscapeCandidate(candidates: List[str]) -> str: - for candidate in candidates: - if isValidInkscape(candidate): - return candidate - raise RuntimeError("No Inkscape executable found. Please check:\n" + - "- if Inkscape is installed\n" + - "- if it is version at least 1.0\n" + - "If the conditions above are true, please ensure Inkscape is in PATH or\n" + - "ensure there is environmental variable 'PCBDRAW_INKSCAPE' pointing to the Inkscape executable\n\n" + - "Checked paths: \n" + - "\n".join([f"- {x}" for x in candidates])) diff --git a/kibot/PcbDraw/convert_unix.py b/kibot/PcbDraw/convert_unix.py deleted file mode 100644 index 1b656185..00000000 --- a/kibot/PcbDraw/convert_unix.py +++ /dev/null @@ -1,37 +0,0 @@ -# Author: Jan Mrázek -# License: MIT -import subprocess -import os -import textwrap -from .convert_common import chooseInkscapeCandidate - -def detectInkscape() -> str: - """ - Return path to working Inkscape >v1.0 executable - """ - candidates = [] - if "PCBDRAW_INKSCAPE" in os.environ: - # Ensure there is the .com extension needed for CLI interface - path = os.path.splitext(os.environ["PCBDRAW_INKSCAPE"])[0] + ".com" - candidates.append(path) - candidates.append("inkscape") # Inkscape in path - return chooseInkscapeCandidate(candidates) - -def rsvgSvgToPng(inputFilename: str, outputFilename: str, dpi: int) -> None: - tool = os.environ.get("PCBDRAW_RSVG", "rsvg-convert") - command = [tool, "--dpi-x", str(dpi), "--dpi-y", str(dpi), - "--output", outputFilename, "--format", "png", inputFilename] - def reportError(message: str) -> None: - raise RuntimeError(f"Cannot convert {inputFilename} to {outputFilename}. RSVG failed with:\n" - + textwrap.indent(message, " ")) - try: - r = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if r.returncode != 0: - output = r.stdout.decode("utf-8") + "\n" + r.stderr.decode("utf-8") - reportError(output) - except subprocess.CalledProcessError as e: - output = e.stdout.decode("utf-8") + "\n" + e.stderr.decode("utf-8") - reportError(output) - except FileNotFoundError: - reportError("rsvg-convert is not available. Please make sure it is installed.\n" + - f"It was executed via invoking '{tool}'") diff --git a/kibot/PcbDraw/convert_windows.py b/kibot/PcbDraw/convert_windows.py deleted file mode 100644 index d3c130a1..00000000 --- a/kibot/PcbDraw/convert_windows.py +++ /dev/null @@ -1,40 +0,0 @@ -# Author: Jan Mrázek -# License: MIT -import os -# Reports false error on Linux as LnkParse3 is Windows-only dependency -import LnkParse3 # type: ignore -from typing import List -from .convert_common import chooseInkscapeCandidate - -def detectInkscape() -> str: - """ - Return path to working Inkscape >v1.0 executable - """ - candidates = [] - if "PCBDRAW_INKSCAPE" in os.environ: - # Ensure there is the .com extension needed for CLI interface - path = os.path.splitext(os.environ["PCBDRAW_INKSCAPE"])[0] + ".com" - candidates.append(path) - candidates.append("inkscape") # Inkscape in path - candidates += readInkscapeFromStartMenu() - - return chooseInkscapeCandidate(candidates) - -def readInkscapeFromStartMenu() -> List[str]: - candidates = [] - for profile in [os.environ.get("ALLUSERSPROFILE", ""), os.environ.get("USERPROFILE", "")]: - path = os.path.join(profile, "Microsoft", "Windows", "Start Menu", - "Programs", "Inkscape", "Inkscape.lnk") - try: - with open(path, "rb") as f: - lnk = LnkParse3.lnk_file(f) - abspath = os.path.realpath(lnk.string_data.relative_path()) - # The .com version provides CLI interface - abspath = os.path.splitext(abspath)[0] + ".com" - candidates.append(abspath) - except FileNotFoundError: - continue - return candidates - -if __name__ == "__main__": - print(detectInkscape()) diff --git a/kibot/out_pcb_print.py b/kibot/out_pcb_print.py index 900ade28..f3d37aba 100644 --- a/kibot/out_pcb_print.py +++ b/kibot/out_pcb_print.py @@ -771,7 +771,6 @@ class PCB_PrintOptions(VariantOptions): def pcbdraw_by_module(self, pcbdraw_file, back): self.ensure_tool('LXML') from .PcbDraw.plot import PcbPlotter, PlotSubstrate - from .PcbDraw.convert import save # Run PcbDraw to make the heavy work (find the Edge.Cuts path and create masks) try: plotter = PcbPlotter(GS.board) @@ -786,7 +785,7 @@ class PCB_PrintOptions(VariantOptions): if GS.debug_level > 1: # Save the SVG only for debug purposes - save(image, pcbdraw_file, 300) + image.write(pcbdraw_file) # Return the SVG as a string from lxml.etree import tostring return tostring(image).decode() diff --git a/kibot/out_pcbdraw.py b/kibot/out_pcbdraw.py index a44b86f4..dd71f444 100644 --- a/kibot/out_pcbdraw.py +++ b/kibot/out_pcbdraw.py @@ -399,20 +399,19 @@ class PcbDrawOptions(VariantOptions): super().run(name) self.ensure_tool('LXML') from .PcbDraw.plot import PcbPlotter, PlotPaste, PlotPlaceholders, PlotSubstrate, PlotVCuts - from .PcbDraw.convert import save # Select a name and format that PcbDraw can handle - save_output_name = name - save_output_format = self.format + svg_save_output_name = save_output_name = name + self.rsvg_command = None self.convert_command = None # Check we have the tools needed for the output format if self.format != 'svg': # We need RSVG for anything other than SVG - self.ensure_tool('RSVG') + self.rsvg_command = self.ensure_tool('RSVG') + svg_save_output_name = _get_tmp_name('.svg') # We need ImageMagick for anything other than SVG and PNG if self.format != 'png': self.convert_command = self.ensure_tool('ImageMagick') save_output_name = _get_tmp_name('.png') - save_output_format = 'png' # Apply any variant self.filter_pcb_components(GS.board, do_3D=True) @@ -468,10 +467,18 @@ class PcbDrawOptions(VariantOptions): exit(PCBDRAW_ERR) # Save the result - logger.debug('Saving output to '+save_output_name) - save(image, save_output_name, self.dpi, format=save_output_format) - # Do we need to convert the saved file? + logger.debug('Saving output to '+svg_save_output_name) + image.write(svg_save_output_name) + # Do we need to convert to PNG? + if self.rsvg_command: + logger.debug('Converting {} -> {}'.format(svg_save_output_name, save_output_name)) + cmd = [self.rsvg_command, '--dpi-x', str(self.dpi), '--dpi-y', str(self.dpi), + '--output', save_output_name, '--format', 'png', svg_save_output_name] + _run_command(cmd) + os.remove(svg_save_output_name) + # Do we need to convert the saved file? (JPG/BMP) if self.convert_command is not None: + logger.debug('Converting {} -> {}'.format(save_output_name, name)) cmd = [self.convert_command, save_output_name] if self.format == 'jpg': cmd += ['-quality', '85%'] diff --git a/tests/yaml_samples/pcbdraw_all_components.kibot.yaml b/tests/yaml_samples/pcbdraw_all_components.kibot.yaml new file mode 100644 index 00000000..3a4e7665 --- /dev/null +++ b/tests/yaml_samples/pcbdraw_all_components.kibot.yaml @@ -0,0 +1,22 @@ +kiplot: + version: 1 + +global: + pcb_finish: ENIG + +outputs: + - name: PcbDraw + comment: "PcbDraw test top" + type: pcbdraw + dir: PcbDraw + options: &pcb_draw_ops + format: bmp + show_components: all + + - name: PcbDraw2 + comment: "PcbDraw test bottom" + type: pcbdraw + dir: PcbDraw + options: + <<: *pcb_draw_ops + bottom: True