navigate_results: Added outputs preview instead of just icons
This commit is contained in:
parent
0eafc93fa8
commit
ae5dea0762
17
README.md
17
README.md
|
|
@ -129,6 +129,13 @@ Notes:
|
|||
[**Colorama**](https://pypi.org/project/Colorama/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-colorama)
|
||||
- Optional to get color messages in a portable way for general use
|
||||
|
||||
[**RSVG tools**](https://cran.r-project.org/web/packages/rsvg/index.html) (tool) [Debian](https://packages.debian.org/bullseye/librsvg2-bin)
|
||||
- Optional to:
|
||||
- Create PDF, PNG, EPS and PS formats for `pcb_print`
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create PNG icons for `navigate_results`
|
||||
- Create PNG and JPG images for `pcbdraw`
|
||||
|
||||
[**Git**](https://git-scm.com/) (tool) [Debian](https://packages.debian.org/bullseye/git)
|
||||
- Optional to:
|
||||
- Find commit hash and/or date for `pcb_replace`
|
||||
|
|
@ -138,15 +145,13 @@ Notes:
|
|||
[**ImageMagick**](https://imagemagick.org/) (tool) [Debian](https://packages.debian.org/bullseye/imagemagick)
|
||||
- Optional to:
|
||||
- Create monochrome prints for `pcb_print`
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create JPG images for `pcbdraw`
|
||||
|
||||
[**RSVG tools**](https://cran.r-project.org/web/packages/rsvg/index.html) (tool) [Debian](https://packages.debian.org/bullseye/librsvg2-bin)
|
||||
- Optional to:
|
||||
- Create PDF, PNG, EPS and PS formats for `pcb_print`
|
||||
- Create PNG and JPG images for `pcbdraw`
|
||||
|
||||
[**Ghostscript**](https://www.ghostscript.com/) (tool) [Debian](https://packages.debian.org/bullseye/ghostscript)
|
||||
- Optional to create PS files for `pcb_print`
|
||||
- Optional to:
|
||||
- Create PS files for `pcb_print`
|
||||
- Create outputs preview for `navigate_results`
|
||||
|
||||
[**Pandoc**](https://pandoc.org/) (tool) [Debian](https://packages.debian.org/bullseye/pandoc)
|
||||
- Optional to create PDF/ODF/DOCX files for `report`
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ W_WRONGOAR = '(W087) '
|
|||
W_ECCLASST = '(W088) '
|
||||
W_PDMASKFAIL = '(W089) '
|
||||
W_MISSTOOL = '(W090) '
|
||||
W_NOTYET = '(W091) '
|
||||
# Somehow arbitrary, the colors are real, but can be different
|
||||
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
|
||||
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",
|
||||
|
|
|
|||
|
|
@ -7,16 +7,32 @@
|
|||
# The rest are KiCad icons
|
||||
import os
|
||||
import subprocess
|
||||
import pprint
|
||||
from shutil import copy2, which
|
||||
from math import ceil
|
||||
from struct import unpack
|
||||
from tempfile import NamedTemporaryFile
|
||||
from .gs import GS
|
||||
import pprint
|
||||
from .optionable import BaseOptions
|
||||
from .kiplot import config_output, get_output_dir
|
||||
from .registrable import RegOutput
|
||||
from .misc import W_NOTYET, W_MISSTOOL, TRY_INSTALL_CHECK, ToolDependencyRole, ToolDependency
|
||||
from .registrable import RegOutput, RegDependency
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from . import log, __version__
|
||||
|
||||
SVGCONV = 'rsvg-convert'
|
||||
CONVERT = 'convert'
|
||||
PS2IMG = 'ghostscript'
|
||||
RegDependency.register(ToolDependency('navigate_results', 'RSVG tools',
|
||||
'https://cran.r-project.org/web/packages/rsvg/index.html', deb='librsvg2-bin',
|
||||
command=SVGCONV,
|
||||
roles=[ToolDependencyRole(desc='Create outputs preview'),
|
||||
ToolDependencyRole(desc='Create PNG icons')]))
|
||||
RegDependency.register(ToolDependency('navigate_results', 'Ghostscript', 'https://www.ghostscript.com/',
|
||||
url_down='https://github.com/ArtifexSoftware/ghostpdl-downloads/releases',
|
||||
roles=ToolDependencyRole(desc='Create outputs preview')))
|
||||
RegDependency.register(ToolDependency('navigate_results', 'ImageMagick', 'https://imagemagick.org/', command='convert',
|
||||
roles=ToolDependencyRole(desc='Create outputs preview')))
|
||||
logger = log.get_logger()
|
||||
CAT_IMAGE = {'PCB': 'pcbnew',
|
||||
'Schematic': 'eeschema',
|
||||
|
|
@ -68,8 +84,9 @@ for i in range(31):
|
|||
EXT_IMAGE['gp'+n] = 'file_gbr'
|
||||
BIG_ICON = 256
|
||||
MID_ICON = 64
|
||||
OUT_COLS = 10
|
||||
SVGCONV = 'rsvg-convert'
|
||||
OUT_COLS = 12
|
||||
BIG_2_MID_REL = int(ceil(BIG_ICON/MID_ICON))
|
||||
IMAGEABLES = {'png', 'jpg', 'pdf', 'eps', 'svg', 'ps'}
|
||||
STYLE = """
|
||||
.cat-table { margin-left: auto; margin-right: auto; }
|
||||
.cat-table td { padding: 20px 24px; }
|
||||
|
|
@ -122,6 +139,15 @@ def svg_to_png(svg_file, png_file, width):
|
|||
return _run_command(cmd)
|
||||
|
||||
|
||||
def get_png_size(file):
|
||||
with open(file, 'rb') as f:
|
||||
s = f.read()
|
||||
if not (s[:8] == b'\x89PNG\r\n\x1a\n' and (s[12:16] == b'IHDR')):
|
||||
return 0, 0
|
||||
w, h = unpack('>LL', s[16:24])
|
||||
return int(w), int(h)
|
||||
|
||||
|
||||
class Navigate_ResultsOptions(BaseOptions):
|
||||
def __init__(self):
|
||||
with document:
|
||||
|
|
@ -145,14 +171,23 @@ class Navigate_ResultsOptions(BaseOptions):
|
|||
node[out.name] = out
|
||||
|
||||
def copy(self, img, width):
|
||||
""" Copy an SVG icon to the images/ dir.
|
||||
Tries to convert it to PNG. """
|
||||
img_w = "{}_{}".format(img, width)
|
||||
if img_w in self.copied_images:
|
||||
# Already copied, just return its name
|
||||
return self.copied_images[img_w]
|
||||
src = os.path.join(self.img_src_dir, 'images', img+'.svg')
|
||||
dst = os.path.join(self.out_dir, 'images', img)
|
||||
dst = os.path.join(self.out_dir, 'images', img_w)
|
||||
id = img_w
|
||||
if self.svg2png_avail and svg_to_png(src, dst+'.png', width):
|
||||
img += '.png'
|
||||
img_w += '.png'
|
||||
else:
|
||||
copy2(src, dst+'.svg')
|
||||
img += '.svg'
|
||||
return os.path.join('images', img)
|
||||
img_w += '.svg'
|
||||
name = os.path.join('images', img_w)
|
||||
self.copied_images[id] = name
|
||||
return name
|
||||
|
||||
def get_image_for_cat(self, cat):
|
||||
if cat in CAT_IMAGE:
|
||||
|
|
@ -162,13 +197,76 @@ class Navigate_ResultsOptions(BaseOptions):
|
|||
format(cat_img, cat))
|
||||
return cat
|
||||
|
||||
def get_image_for_file(self, file):
|
||||
def compose_image(self, file, ext, img, out_name):
|
||||
if not os.path.isfile(file):
|
||||
logger.warning(W_NOTYET+"{} not yet generated, using an icon".format(os.path.relpath(file)))
|
||||
return False, None, None
|
||||
if ext == 'svg' and not self.svg2png_avail:
|
||||
logger.warning(W_MISSTOOL+"Missing SVG to PNG converter: {}"+SVGCONV)
|
||||
logger.warning(W_MISSTOOL+TRY_INSTALL_CHECK)
|
||||
return False, None, None
|
||||
if ext == 'ps' and not self.ps2img_avail:
|
||||
logger.warning(W_MISSTOOL+"Missing PS to PNG converter: {}"+PS2IMG)
|
||||
logger.warning(W_MISSTOOL+TRY_INSTALL_CHECK)
|
||||
return False, None, None
|
||||
# Create a unique name using the output name and the generated file name
|
||||
bfname = os.path.splitext(os.path.basename(file))[0]
|
||||
fname = os.path.join(self.out_dir, 'images', out_name+'_'+bfname+'.png')
|
||||
# Full path for the icon image
|
||||
icon = os.path.join(self.out_dir, img)
|
||||
if ext == 'pdf':
|
||||
# Only page 1
|
||||
file += '[0]'
|
||||
if ext == 'svg':
|
||||
with NamedTemporaryFile(mode='w', suffix='.png', delete=False) as f:
|
||||
tmp_name = f.name
|
||||
logger.debug('Temporal convert: {} -> {}'.format(file, tmp_name))
|
||||
if not svg_to_png(file, tmp_name, BIG_ICON):
|
||||
return False, None, None
|
||||
file = tmp_name
|
||||
cmd = [CONVERT, file,
|
||||
# Size for the big icons (width)
|
||||
'-resize', str(BIG_ICON)+'x',
|
||||
# Add the file type icon
|
||||
icon,
|
||||
# At the bottom right
|
||||
'-gravity', 'south-east',
|
||||
# This is a composition, not 2 images
|
||||
'-composite',
|
||||
fname]
|
||||
res = _run_command(cmd)
|
||||
if ext == 'svg':
|
||||
logger.debug('Removing temporal {}'.format(tmp_name))
|
||||
os.remove(tmp_name)
|
||||
return res, fname, os.path.relpath(fname, start=self.out_dir)
|
||||
|
||||
def get_image_for_file(self, file, out_name, use_big=False):
|
||||
ext = os.path.splitext(file)[1][1:].lower()
|
||||
wide = False
|
||||
# Copy the icon for this file extension
|
||||
img = self.copy(EXT_IMAGE.get(ext, 'unknown'), MID_ICON)
|
||||
ext_img = '<img src="{}" alt="{}" width="{}" height="{}">'.format(img, file, MID_ICON, MID_ICON)
|
||||
# Full name for the file
|
||||
file_full = file
|
||||
# Just the file, to display it
|
||||
file = os.path.basename(file)
|
||||
# The icon size
|
||||
height = width = MID_ICON
|
||||
# Check if this file can be represented by an image
|
||||
if use_big and self.convert_avail and ext in IMAGEABLES:
|
||||
# Try to compose the image of the file with the icon
|
||||
ok, fimg, new_img = self.compose_image(file_full, ext, img, out_name)
|
||||
if ok:
|
||||
# It was converted, replace the icon by the composited image
|
||||
img = new_img
|
||||
# Compute its size
|
||||
width, height = get_png_size(fimg)
|
||||
# We are using the big size
|
||||
wide = True
|
||||
# Now add the image with its file name as caption
|
||||
ext_img = '<img src="{}" alt="{}" width="{}" height="{}">'.format(img, file, width, height)
|
||||
file = ('<table class="out-img"><tr><td>{}</td></tr><tr><td class="td-small">{}</td></tr></table>'.
|
||||
format(ext_img, file))
|
||||
return file
|
||||
return file, wide
|
||||
|
||||
def add_back_home(self, f, prev):
|
||||
if prev is not None:
|
||||
|
|
@ -226,6 +324,7 @@ class Navigate_ResultsOptions(BaseOptions):
|
|||
name, ext = os.path.splitext(name)
|
||||
for oname, out in node.items():
|
||||
f.write('<table class="output-table">\n')
|
||||
out_name = oname.replace(' ', '_')
|
||||
oname = oname.replace('_', ' ')
|
||||
oname = oname[0].upper()+oname[1:]
|
||||
if out.comment:
|
||||
|
|
@ -236,22 +335,37 @@ class Navigate_ResultsOptions(BaseOptions):
|
|||
f.write('<tbody><tr>\n')
|
||||
targets = out.get_targets(out_dir)
|
||||
if len(targets) == 1:
|
||||
tg = os.path.relpath(os.path.abspath(targets[0]), start=self.out_dir)
|
||||
tg_rel = os.path.relpath(os.path.abspath(targets[0]), start=self.out_dir)
|
||||
img, _ = self.get_image_for_file(targets[0], out_name, use_big=True)
|
||||
f.write('<td class="out-cell" colspan="{}"><a href="{}">{}</a></td>\n'.
|
||||
format(OUT_COLS, tg, self.get_image_for_file(os.path.basename(tg))))
|
||||
format(OUT_COLS, tg_rel, img))
|
||||
else:
|
||||
c = 0
|
||||
for tg in targets:
|
||||
if c == OUT_COLS:
|
||||
f.write('</tr>\n<tr>\n')
|
||||
c = 0
|
||||
tg = os.path.relpath(os.path.abspath(tg), start=self.out_dir)
|
||||
f.write('<td class="out-cell"><a href="{}">{}</a></td>\n'.
|
||||
format(tg, self.get_image_for_file(os.path.basename(tg))))
|
||||
c = c+1
|
||||
for _ in range(c, OUT_COLS):
|
||||
f.write('<td class="out-cell"></td>\n')
|
||||
f.write('</tr></tbody>\n')
|
||||
tg_rel = os.path.relpath(os.path.abspath(tg), start=self.out_dir)
|
||||
img, wide = self.get_image_for_file(tg, out_name, use_big=True)
|
||||
# Check if we need to break this row
|
||||
span = 1
|
||||
if wide:
|
||||
span = BIG_2_MID_REL
|
||||
remain = OUT_COLS-c
|
||||
if span > remain:
|
||||
f.write('<td class="out-cell" colspan="{}"></td></tr>\n<tr>\n'.format(remain))
|
||||
# Add a new cell
|
||||
f.write('<td class="out-cell" colspan="{}"><a href="{}">{}</a></td>\n'.format(span, tg_rel, img))
|
||||
c = c+span
|
||||
if c < OUT_COLS:
|
||||
f.write('<td class="out-cell" colspan="{}"></td>\n'.format(OUT_COLS-c))
|
||||
f.write('</tr>\n')
|
||||
# This row is just to ensure we have at least 1 cell in each column
|
||||
f.write('<tr>\n')
|
||||
for _ in range(OUT_COLS):
|
||||
f.write('<td></td>\n')
|
||||
f.write('</tr>\n')
|
||||
f.write('</tbody>\n')
|
||||
f.write('</table>\n')
|
||||
self.add_back_home(f, prev)
|
||||
f.write('</body>\n</html>\n')
|
||||
|
|
@ -268,6 +382,7 @@ class Navigate_ResultsOptions(BaseOptions):
|
|||
self.img_src_dir = os.path.dirname(__file__)
|
||||
self.img_dst_dir = os.path.join(self.out_dir, 'images')
|
||||
os.makedirs(self.img_dst_dir, exist_ok=True)
|
||||
self.copied_images = {}
|
||||
name = os.path.basename(name)
|
||||
# Create a tree with all the outputs
|
||||
o_tree = {}
|
||||
|
|
@ -283,6 +398,8 @@ class Navigate_ResultsOptions(BaseOptions):
|
|||
with open(os.path.join(self.out_dir, 'styles.css'), 'wt') as f:
|
||||
f.write(STYLE)
|
||||
self.svg2png_avail = which(SVGCONV) is not None
|
||||
self.convert_avail = which(CONVERT) is not None
|
||||
self.ps2img_avail = which(PS2IMG) is not None
|
||||
# Create the pages
|
||||
self.home = name
|
||||
self.back_img = self.copy('back', MID_ICON)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ deps = '{\
|
|||
"deb_package": "ghostscript",\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 1,\
|
||||
"importance": 2,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -92,6 +92,12 @@ deps = '{\
|
|||
"mandatory": false,\
|
||||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create outputs preview",\
|
||||
"mandatory": false,\
|
||||
"output": "navigate_results",\
|
||||
"version": null\
|
||||
}\
|
||||
],\
|
||||
"url": "https://www.ghostscript.com/",\
|
||||
|
|
@ -140,7 +146,7 @@ deps = '{\
|
|||
"deb_package": "imagemagick",\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 2,\
|
||||
"importance": 3,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -157,6 +163,12 @@ deps = '{\
|
|||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create outputs preview",\
|
||||
"mandatory": false,\
|
||||
"output": "navigate_results",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create JPG images",\
|
||||
"mandatory": false,\
|
||||
|
|
@ -553,7 +565,7 @@ deps = '{\
|
|||
"deb_package": "librsvg2-bin",\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 2,\
|
||||
"importance": 4,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -570,6 +582,18 @@ deps = '{\
|
|||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create outputs preview",\
|
||||
"mandatory": false,\
|
||||
"output": "navigate_results",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create PNG icons",\
|
||||
"mandatory": false,\
|
||||
"output": "navigate_results",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create PNG and JPG images",\
|
||||
"mandatory": false,\
|
||||
|
|
|
|||
Loading…
Reference in New Issue