[PCB Print] Changed the conversion strategy
- Now we can control the resolution - Added option for this Related to #259
This commit is contained in:
parent
22d618a425
commit
3684889060
|
|
@ -26,7 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
KiCad bug [11562](https://gitlab.com/kicad/code/kicad/-/issues/11562)
|
||||
- Internal BoM: KiCad 6 text variables expansion in the fields (#247)
|
||||
- Compress: Option to store symlinks. (See #265)
|
||||
- PCB Print: Option to configure the forced edge color. (#281)
|
||||
- PCB Print:
|
||||
- Option to configure the forced edge color. (#281)
|
||||
- Option to control the resolution (DPI). (See #259)
|
||||
|
||||
### Fixed
|
||||
- OAR computation (Report) (#225)
|
||||
|
|
@ -36,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Problems with zones on multiple layers (#226)
|
||||
- Problems with `hide_excluded: true` and components not in the SCH (#258)
|
||||
- Text vars generated in the same run didn't show up (#280)
|
||||
- Low resolution for the solder mask. (See #259)
|
||||
- SCH Variants on KiCad 6: Problems with missing values in the title block.
|
||||
- Report: Converted file wasn't stored at `dir` (#238)
|
||||
- Datasheet download: Time-outs on some servers expecting modern browsers (#240)
|
||||
|
|
@ -53,6 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
kiplot/kibot, import, global, filters, variants, preflight, outputs
|
||||
- Datasheet download:
|
||||
- Continue downloading if an SSL certificate error found (#239)
|
||||
- PCB_Print: PNGs no longer has transparent background. This is because now we
|
||||
use a PDF as intermediate step.
|
||||
|
||||
|
||||
## [1.2.0] - 2022-06-15
|
||||
|
|
|
|||
23
README.md
23
README.md
|
|
@ -155,14 +155,6 @@ Notes:
|
|||
[**Colorama**](https://pypi.org/project/Colorama/) [](https://pypi.org/project/Colorama/) [](https://pypi.org/project/Colorama/) [](https://packages.debian.org/bullseye/python3-colorama)
|
||||
- Optional to get color messages in a portable way for general use
|
||||
|
||||
[**RSVG tools**](https://gitlab.gnome.org/GNOME/librsvg) v2.40 [](https://gitlab.gnome.org/GNOME/librsvg) [](https://packages.debian.org/bullseye/librsvg2-bin) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create PNG icons for `navigate_results`
|
||||
- Create PDF, PNG and PS formats for `pcb_print`
|
||||
- Create EPS format for `pcb_print` (v2.40)
|
||||
- Create PNG and JPG images for `pcbdraw`
|
||||
|
||||
[**Git**](https://git-scm.com/) [](https://git-scm.com/) [](https://packages.debian.org/bullseye/git) 
|
||||
- Optional to:
|
||||
- Compare with files in the repo for `diff`
|
||||
|
|
@ -170,16 +162,23 @@ Notes:
|
|||
- Find commit hash and/or date for `sch_replace`
|
||||
- Find commit hash and/or date for `set_text_variables`
|
||||
|
||||
[**RSVG tools**](https://gitlab.gnome.org/GNOME/librsvg) [](https://gitlab.gnome.org/GNOME/librsvg) [](https://packages.debian.org/bullseye/librsvg2-bin) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create PNG icons for `navigate_results`
|
||||
- Create PDF, PNG, PS and EPS formats for `pcb_print`
|
||||
- Create PNG and JPG images for `pcbdraw`
|
||||
|
||||
[**ImageMagick**](https://imagemagick.org/) [](https://imagemagick.org/) [](https://packages.debian.org/bullseye/imagemagick) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create monochrome prints for `pcb_print`
|
||||
- Create monochrome prints and scaled PNG files for `pcb_print`
|
||||
- Create JPG images for `pcbdraw`
|
||||
|
||||
[**Ghostscript**](https://www.ghostscript.com/) [](https://www.ghostscript.com/) [](https://packages.debian.org/bullseye/ghostscript) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create PS files for `pcb_print`
|
||||
- Create PNG, PS and EPS formats for `pcb_print`
|
||||
|
||||
[**Pandoc**](https://pandoc.org/) [](https://pandoc.org/) [](https://packages.debian.org/bullseye/pandoc)
|
||||
- Optional to create PDF/ODF/DOCX files for `report`
|
||||
|
|
@ -2226,6 +2225,8 @@ Notes:
|
|||
- `colored_vias`: [boolean=true] Plot vias in a different color. Like KiCad GUI does.
|
||||
- `dnf_filter`: [string|list(string)='_none'] Name of the filter to mark components as not fitted.
|
||||
A short-cut to use for simple cases where a variant is an overkill.
|
||||
- `dpi`: [number=360] [36,1200] Resolution (Dots Per Inch) for the output file. Most objects are vectors, but thing
|
||||
like the the solder mask are handled as images by the conversion tools.
|
||||
- `drill_marks`: [string='full'] [none,small,full] What to use to indicate the drill places, can be none, small or full (for real scale).
|
||||
- `forced_edge_cuts_color`: [string=''] Color used for the `force_edge_cuts` option.
|
||||
- `frame_plot_mechanism`: [string='internal'] [gui,internal,plot] Plotting the frame from Python is problematic.
|
||||
|
|
@ -2240,7 +2241,7 @@ Notes:
|
|||
- `keep_temporal_files`: [boolean=false] Store the temporal page and layer files in the output dir and don't delete them.
|
||||
- `micro_via_color`: [string=''] Color used for micro `colored_vias`.
|
||||
- `pad_color`: [string=''] Color used for `colored_pads`.
|
||||
- `png_width`: [number=1280] Width of the PNG in pixels.
|
||||
- `png_width`: [number=1280] [0,7680] Width of the PNG in pixels. Use 0 to use as many pixels as the DPI needs for the page size.
|
||||
- `realistic_solder_mask`: [boolean=true] Try to draw the solder mask as a real solder mask, not the negative used for fabrication.
|
||||
In order to get a good looking select a color with transparency, i.e. '#14332440'.
|
||||
PcbDraw must be installed in order to use this option.
|
||||
|
|
|
|||
|
|
@ -1086,6 +1086,9 @@ outputs:
|
|||
# [string|list(string)='_none'] Name of the filter to mark components as not fitted.
|
||||
# A short-cut to use for simple cases where a variant is an overkill
|
||||
dnf_filter: '_none'
|
||||
# [number=360] [36,1200] Resolution (Dots Per Inch) for the output file. Most objects are vectors, but thing
|
||||
# like the the solder mask are handled as images by the conversion tools
|
||||
dpi: 360
|
||||
# [string='full'] [none,small,full] What to use to indicate the drill places, can be none, small or full (for real scale)
|
||||
drill_marks: 'full'
|
||||
# [boolean=false] Add the `Edge.Cuts` to all the pages
|
||||
|
|
@ -1164,7 +1167,7 @@ outputs:
|
|||
title: ''
|
||||
# [boolean=true] Include the title-block (worksheet, frame, etc.)
|
||||
plot_sheet_reference: true
|
||||
# [number=1280] Width of the PNG in pixels
|
||||
# [number=1280] [0,7680] Width of the PNG in pixels. Use 0 to use as many pixels as the DPI needs for the page size
|
||||
png_width: 1280
|
||||
# [boolean=true] Try to draw the solder mask as a real solder mask, not the negative used for fabrication.
|
||||
# In order to get a good looking select a color with transparency, i.e. '#14332440'.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
from . import PyPDF2
|
||||
|
||||
|
||||
def create_pdf_from_pages(input_files, output_fn):
|
||||
def create_pdf_from_pages(input_files, output_fn, scale=1.0):
|
||||
output = PyPDF2.PdfFileWriter()
|
||||
# Collect all pages
|
||||
open_files = []
|
||||
|
|
@ -17,6 +17,8 @@ def create_pdf_from_pages(input_files, output_fn):
|
|||
open_files.append(file)
|
||||
pdf_reader = PyPDF2.PdfFileReader(file)
|
||||
page_obj = pdf_reader.getPage(0)
|
||||
if scale != 1.0:
|
||||
page_obj.scaleBy(scale)
|
||||
page_obj.compressContentStreams()
|
||||
output.addPage(page_obj)
|
||||
# Write all pages to a file
|
||||
|
|
|
|||
|
|
@ -400,12 +400,12 @@ class BaseOptions(Optionable):
|
|||
""" Looks for a dependency """
|
||||
return GS.check_tool_dep(self._parent.type, name, fatal=False)
|
||||
|
||||
def expand_filename(self, dir, name, id, ext):
|
||||
def expand_filename(self, dir, name, id, ext, make_safe=True):
|
||||
cur_id = self._expand_id
|
||||
cur_ext = self._expand_ext
|
||||
self._expand_id = id
|
||||
self._expand_ext = ext
|
||||
name = self.expand_filename_both(name, is_sch=self._parent._sch_related)
|
||||
name = self.expand_filename_both(name, is_sch=self._parent._sch_related, make_safe=make_safe)
|
||||
res = os.path.abspath(os.path.join(dir, name))
|
||||
self._expand_id = cur_id
|
||||
self._expand_ext = cur_ext
|
||||
|
|
|
|||
|
|
@ -7,16 +7,12 @@
|
|||
"""
|
||||
Dependencies:
|
||||
- from: RSVG
|
||||
role: Create PDF, PNG and PS formats
|
||||
role: Create PDF, PNG, PS and EPS formats
|
||||
id: rsvg1
|
||||
- from: RSVG
|
||||
role: Create EPS format
|
||||
version: '2.40'
|
||||
id: rsvg2
|
||||
- from: Ghostscript
|
||||
role: Create PS files
|
||||
role: Create PNG, PS and EPS formats
|
||||
- from: ImageMagick
|
||||
role: Create monochrome prints
|
||||
role: Create monochrome prints and scaled PNG files
|
||||
- from: PcbDraw
|
||||
role: Create realistic solder masks
|
||||
# The plot_frame_gui() needs KiAuto to print the frame
|
||||
|
|
@ -31,6 +27,13 @@ Dependencies:
|
|||
role: mandatory
|
||||
downloader: python
|
||||
"""
|
||||
# Direct SVG to EPS conversion is problematic.
|
||||
# If we use 72 dpi the page size is ok, but some objects (currently the solder mask) have low resolution.
|
||||
# So we create a PDF and then use GS to create the EPS files.
|
||||
# - from: RSVG
|
||||
# role: Create EPS format
|
||||
# version: '2.40'
|
||||
# id: rsvg2
|
||||
import re
|
||||
import os
|
||||
import subprocess
|
||||
|
|
@ -247,7 +250,10 @@ class PCB_PrintOptions(VariantOptions):
|
|||
""" *[PDF,SVG,PNG,EPS,PS] Format for the output file/s.
|
||||
Note that for PS you need `ghostscript` which isn't part of the default docker images """
|
||||
self.png_width = 1280
|
||||
""" Width of the PNG in pixels """
|
||||
""" [0,7680] Width of the PNG in pixels. Use 0 to use as many pixels as the DPI needs for the page size """
|
||||
self.dpi = 360
|
||||
""" [36,1200] Resolution (Dots Per Inch) for the output file. Most objects are vectors, but thing
|
||||
like the the solder mask are handled as images by the conversion tools """
|
||||
self.colored_pads = True
|
||||
""" Plot through-hole in a different color. Like KiCad GUI does """
|
||||
self.pad_color = ''
|
||||
|
|
@ -860,40 +866,66 @@ class PCB_PrintOptions(VariantOptions):
|
|||
|
||||
def svg_to_pdf(self, input_folder, svg_file, pdf_file):
|
||||
# Note: rsvg-convert uses 90 dpi but KiCad (and the docs I found) says SVG pt is 72 dpi
|
||||
cmd = [self.rsvg_command, '-d', '72', '-p', '72', '-f', 'pdf', '-o', os.path.join(input_folder, pdf_file),
|
||||
# We use a 5x scale and then reduce it to maintain the page size
|
||||
dpi = str(self.dpi)
|
||||
cmd = [self.rsvg_command, '-d', dpi, '-p', dpi, '-f', 'pdf', '-o', os.path.join(input_folder, pdf_file),
|
||||
os.path.join(input_folder, svg_file)]
|
||||
_run_command(cmd)
|
||||
|
||||
def svg_to_png(self, input_folder, svg_file, png_file, width):
|
||||
cmd = [self.rsvg_command, '-w', str(width), '-f', 'png', '-o', os.path.join(input_folder, png_file),
|
||||
os.path.join(input_folder, svg_file)]
|
||||
# We can't control the resolution in this way
|
||||
# def svg_to_png(self, input_folder, svg_file, png_file, width):
|
||||
# cmd = [self.rsvg_command, '-w', str(width), '-f', 'png', '-o', os.path.join(input_folder, png_file),
|
||||
# os.path.join(input_folder, svg_file)]
|
||||
# _run_command(cmd)
|
||||
|
||||
# Poor resolution or wrong page size, using a PDF + GS solves it
|
||||
# def svg_to_eps(self, input_folder, svg_file, eps_file):
|
||||
# cmd = [self.rsvg_command_eps, '-d', '72', '-p', '72', '-f', 'eps', '-o', os.path.join(input_folder, eps_file),
|
||||
# os.path.join(input_folder, svg_file)]
|
||||
# _run_command(cmd)
|
||||
|
||||
def run_gs(self, pdf_file, output, device, use_dpi=False):
|
||||
cmd = [self.gs_command, '-q', '-dNOPAUSE', '-dBATCH', '-P-', '-dSAFER', '-sDEVICE='+device,
|
||||
'-sOutputFile='+output, '-c', 'save', 'pop', '-f', pdf_file]
|
||||
if use_dpi:
|
||||
cmd.insert(1, '-r'+str(self.dpi))
|
||||
_run_command(cmd)
|
||||
|
||||
def svg_to_eps(self, input_folder, svg_file, eps_file):
|
||||
cmd = [self.rsvg_command_eps, '-d', '72', '-p', '72', '-f', 'eps', '-o', os.path.join(input_folder, eps_file),
|
||||
os.path.join(input_folder, svg_file)]
|
||||
_run_command(cmd)
|
||||
def pdf_to_ps(self, pdf_file, output):
|
||||
self.run_gs(pdf_file, output, 'ps2write')
|
||||
|
||||
def pdf_to_ps(self, ps_file, output):
|
||||
cmd = [self.gs_command, '-q', '-dNOPAUSE', '-dBATCH', '-P-', '-dSAFER', '-sDEVICE=ps2write', '-sOutputFile='+output,
|
||||
'-c', 'save', 'pop', '-f', ps_file]
|
||||
_run_command(cmd)
|
||||
def pdf_to_eps(self, pdf_file, output):
|
||||
self.run_gs(pdf_file, output, 'eps2write')
|
||||
|
||||
def pdf_to_png(self, pdf_file, output):
|
||||
self.run_gs(pdf_file, output, 'png16m', use_dpi=True)
|
||||
if self.png_width:
|
||||
# Adjust the width
|
||||
convert_command = self.ensure_tool('ImageMagick')
|
||||
size = str(self.png_width)+'x'
|
||||
for n in range(len(self.pages)):
|
||||
file = output % (n+1)
|
||||
cmd = [convert_command, file, '-resize', size, file]
|
||||
_run_command(cmd)
|
||||
|
||||
def create_pdf_from_svg_pages(self, input_folder, input_files, output_fn):
|
||||
""" Convert individual SVG files into individual PDF files using 360 dpi.
|
||||
Then join the individual PDF files into one PDF file scaled to the right page size. """
|
||||
svg_files = []
|
||||
for svg_file in input_files:
|
||||
pdf_file = svg_file.replace('.svg', '.pdf')
|
||||
logger.debug('- Creating {} from {}'.format(pdf_file, svg_file))
|
||||
self.svg_to_pdf(input_folder, svg_file, pdf_file)
|
||||
svg_files.append(os.path.join(input_folder, pdf_file))
|
||||
create_pdf_from_pages(svg_files, output_fn)
|
||||
logger.debug('- Joining {} into {}'.format(svg_files, output_fn))
|
||||
create_pdf_from_pages(svg_files, output_fn, scale=72.0/self.dpi)
|
||||
|
||||
def check_tools(self):
|
||||
if self.format != 'SVG':
|
||||
self.rsvg_command = self.ensure_tool('rsvg1')
|
||||
if self.format == 'PS':
|
||||
self.gs_command = self.ensure_tool('Ghostscript')
|
||||
if self.format == 'EPS':
|
||||
self.rsvg_command_eps = self.ensure_tool('rsvg2')
|
||||
# if self.format == 'EPS':
|
||||
# self.rsvg_command_eps = self.ensure_tool('rsvg2')
|
||||
|
||||
def generate_output(self, output):
|
||||
self.check_tools()
|
||||
|
|
@ -990,24 +1022,31 @@ class PCB_PrintOptions(VariantOptions):
|
|||
assembly_file = GS.pcb_basename+".svg"
|
||||
logger.debug('- Merging layers to {}'.format(assembly_file))
|
||||
self.merge_svg(temp_dir, filelist, temp_dir, assembly_file, p)
|
||||
if self.format in ['PNG', 'EPS']:
|
||||
id = self._expand_id+('_page_'+page_str)
|
||||
out_file = self.expand_filename(output_dir, self.output, id, self._expand_ext)
|
||||
if self.format == 'PNG':
|
||||
self.svg_to_png(temp_dir, assembly_file, out_file, self.png_width)
|
||||
else:
|
||||
self.svg_to_eps(temp_dir, assembly_file, out_file)
|
||||
pages.append(os.path.join(page_str, assembly_file))
|
||||
self.restore_title()
|
||||
# Join all pages in one file
|
||||
if self.format in ['PDF', 'PS']:
|
||||
logger.debug('- Creating output file {}'.format(output))
|
||||
if self.format != 'SVG':
|
||||
if self.format == 'PDF':
|
||||
logger.debug('- Creating output file {}'.format(output))
|
||||
self.create_pdf_from_svg_pages(temp_dir_base, pages, output)
|
||||
else:
|
||||
ps_file = os.path.join(temp_dir, GS.pcb_basename+'.ps')
|
||||
self.create_pdf_from_svg_pages(temp_dir_base, pages, ps_file)
|
||||
self.pdf_to_ps(ps_file, output)
|
||||
logger.debug('- Creating output files')
|
||||
# PS and EPS using Ghostscript
|
||||
# Create a PDF (but in a temporal place)
|
||||
pdf_file = os.path.join(temp_dir, GS.pcb_basename+'_joined.pdf')
|
||||
self.create_pdf_from_svg_pages(temp_dir_base, pages, pdf_file)
|
||||
if self.format == 'PS':
|
||||
# Use GS to create one PS
|
||||
self.pdf_to_ps(pdf_file, output)
|
||||
else: # EPS and PNG
|
||||
id = self._expand_id+('_page_%02d')
|
||||
out_file = self.expand_filename(output_dir, self.output, id, self._expand_ext, make_safe=False)
|
||||
if self.format == 'EPS':
|
||||
# Use GS to create one EPS per page
|
||||
self.pdf_to_eps(pdf_file, out_file)
|
||||
else:
|
||||
# Use GS to create one PNG per page and then scale to the wanted width
|
||||
self.pdf_to_png(pdf_file, out_file)
|
||||
# Remove the temporal files
|
||||
if not self.keep_temporal_files:
|
||||
rmtree(temp_dir_base)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ deps = '{\
|
|||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create PS files",\
|
||||
"desc": "Create PNG, PS and EPS formats",\
|
||||
"mandatory": false,\
|
||||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
|
|
@ -156,7 +156,7 @@ deps = '{\
|
|||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create monochrome prints",\
|
||||
"desc": "Create monochrome prints and scaled PNG files",\
|
||||
"mandatory": false,\
|
||||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
|
|
@ -680,7 +680,7 @@ deps = '{\
|
|||
"downloader_str": "rsvg",\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 5,\
|
||||
"importance": 4,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -704,20 +704,11 @@ deps = '{\
|
|||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create PDF, PNG and PS formats",\
|
||||
"desc": "Create PDF, PNG, PS and EPS formats",\
|
||||
"mandatory": false,\
|
||||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create EPS format",\
|
||||
"mandatory": false,\
|
||||
"output": "pcb_print",\
|
||||
"version": [\
|
||||
2,\
|
||||
40\
|
||||
]\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create PNG and JPG images",\
|
||||
"mandatory": false,\
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
|
|
@ -576,9 +576,9 @@ class TestContext(object):
|
|||
cmd = ['pdfinfo', file]
|
||||
logging.debug('Analyzing PDF size: '+usable_cmd(cmd))
|
||||
res = subprocess.run(cmd, stderr=subprocess.STDOUT, check=True, stdout=subprocess.PIPE).stdout.decode()
|
||||
m = re.search(r'Page size:\s+(\d+) x (\d+) pts', res)
|
||||
assert m is not None
|
||||
return int(m.group(1))/72.0*25.4, int(m.group(2))/72.0*25.4
|
||||
m = re.search(r'Page size:\s+([\d\.]+) x ([\d\.]+) pts', res)
|
||||
assert m is not None, res
|
||||
return float(m.group(1))/72.0*25.4, float(m.group(2))/72.0*25.4
|
||||
|
||||
def expect_gerber_flash_at(self, file, res, pos):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue