Added support for frame to `pcb_print` on KiCad 5
- This is tricky because plot API doesn't support it. So we use `pcbnew_do` to generate an SVG and then we make a vectorized PDF from it.
This commit is contained in:
parent
a5c848c752
commit
510279a0a3
|
|
@ -1542,6 +1542,7 @@ Next time you need this list just use an alias, like this:
|
|||
- `pages`: [list(dict)] List of pages to include in the output document.
|
||||
Each page contains one or more layers of the PCB.
|
||||
* Valid keys:
|
||||
- `black_holes`: [boolean=true] Change the drill holes to be black instead of white.
|
||||
- `exclude_pads_from_silkscreen`: [boolean=false] Do not plot the component pads in the silk screen (KiCad 5.x only).
|
||||
- `layers`: [list(dict)] List of layers printed in this page. Order is important, the last goes on top.
|
||||
* Valid keys:
|
||||
|
|
@ -1559,6 +1560,7 @@ Next time you need this list just use an alias, like this:
|
|||
- `scaling`: [number=1.0] Scale factor (0 means autoscaling).
|
||||
- `sheet`: [string='Assembly'] Text to use for the `sheet` in the title block.
|
||||
- `sheet_reference_color`: [string=''] Color to use for the frame and title block.
|
||||
- `sort_layers`: [boolean=false] Try to sort the layers in the same order that uses KiCad for printing.
|
||||
- `tent_vias`: [boolean=true] Cover the vias.
|
||||
- `title`: [string=''] Text used to replace the sheet title. %VALUE expansions are allowed.
|
||||
If it starts with `+` the text is concatenated.
|
||||
|
|
|
|||
|
|
@ -974,8 +974,10 @@ outputs:
|
|||
# [list(dict)] List of pages to include in the output document.
|
||||
# Each page contains one or more layers of the PCB
|
||||
pages:
|
||||
# [boolean=false] Do not plot the component pads in the silk screen (KiCad 5.x only)
|
||||
- exclude_pads_from_silkscreen: false
|
||||
# [boolean=true] Change the drill holes to be black instead of white
|
||||
- black_holes: true
|
||||
# [boolean=false] Do not plot the component pads in the silk screen (KiCad 5.x only)
|
||||
exclude_pads_from_silkscreen: false
|
||||
# [list(dict)] List of layers printed in this page. Order is important, the last goes on top
|
||||
layers:
|
||||
# [string=''] Color used for this layer
|
||||
|
|
@ -1006,6 +1008,8 @@ outputs:
|
|||
sheet: 'Assembly'
|
||||
# [string=''] Color to use for the frame and title block
|
||||
sheet_reference_color: ''
|
||||
# [boolean=false] Try to sort the layers in the same order that uses KiCad for printing
|
||||
sort_layers: false
|
||||
# [boolean=true] Cover the vias
|
||||
tent_vias: true
|
||||
# [string=''] Text used to replace the sheet title. %VALUE expansions are allowed.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2022 Salvador E. Tropea
|
||||
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
|
||||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
# Note about the size:
|
||||
# A4 landscape 841.889764 x 595.275591 pt = 297 x 210 mm
|
||||
# 1 pt = 4/3*px 1 px = 1"/96 => 1 pt = 4/3 * 1"/96 = 4"/288 = 1"/72 (72 dpi)
|
||||
# 1" = 25.4 mm => 1 pt = 25.4/72 mm = 0.3527777778 mm
|
||||
import re
|
||||
from .. import log
|
||||
|
||||
logger = log.get_logger()
|
||||
|
||||
|
||||
def patch_svg_file(file, remove_bkg=False):
|
||||
logger.debug('Patching SVG file `{}`'.format(file))
|
||||
with open(file, 'rt') as f:
|
||||
text = f.read()
|
||||
text = re.sub(r'<svg (.*) width="(.*)" height="(.*)" viewBox="(\S+) (\S+) (\S+) (\S+)"',
|
||||
r'<svg \1 width="\3" height="\2" viewBox="\4 \5 \7 \6"', text)
|
||||
if remove_bkg:
|
||||
text = re.sub(r'<rect.*>', '', text)
|
||||
else:
|
||||
text = re.sub(r'<rect x="(\S+)" y="(\S+)" width="(\S+)" height="(\S+)"',
|
||||
r'<rect x="\1" y="\2" width="\4" height="\3"', text)
|
||||
with open(file, 'wt') as f:
|
||||
f.write(text)
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
# Adapted from: https://gitlab.com/dennevi/Board2Pdf/
|
||||
# Note: Original code released as Public Domain
|
||||
import os
|
||||
import subprocess
|
||||
from pcbnew import PLOT_CONTROLLER, PLOT_FORMAT_PDF, FromMM
|
||||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
|
|
@ -15,6 +16,9 @@ from .gs import GS
|
|||
from .optionable import Optionable
|
||||
from .out_base import VariantOptions
|
||||
from .kicad.color_theme import load_color_theme
|
||||
from .kicad.patch_svg import patch_svg_file
|
||||
from .misc import CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT
|
||||
from .kiplot import check_script, exec_with_retry, add_extra_options
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from .layer import Layer, get_priority
|
||||
from . import PyPDF2
|
||||
|
|
@ -23,10 +27,19 @@ from . import log
|
|||
logger = log.get_logger()
|
||||
|
||||
# TODO:
|
||||
# - Cache de colores
|
||||
# - Frame en k5?
|
||||
# - SVG?
|
||||
# - rsvg-convert -f pdf -o pp.pdf simple_2layer-F_Cu.svg
|
||||
|
||||
|
||||
def _run_command(cmd):
|
||||
logger.debug('Executing: '+str(cmd))
|
||||
try:
|
||||
cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error('Failed to run %s, error %d', cmd[0], e.returncode)
|
||||
if e.output:
|
||||
logger.debug('Output from command: '+e.output.decode())
|
||||
exit(PDF_PCB_PRINT)
|
||||
logger.debug('Output from command:\n'+cmd_output.decode())
|
||||
|
||||
|
||||
def hex_to_rgb(value):
|
||||
|
|
@ -255,7 +268,7 @@ class PCB_PrintOptions(VariantOptions):
|
|||
To use the KiCad 6 default colors select `_builtin_default`.
|
||||
Usually user colors are stored as `user`, but you can give it another name """
|
||||
self.plot_sheet_reference = True
|
||||
""" Include the title-block. Only available on KiCad 6. """
|
||||
""" Include the title-block """
|
||||
self.pages = PagesOptions
|
||||
""" [list(dict)] List of pages to include in the output document.
|
||||
Each page contains one or more layers of the PCB """
|
||||
|
|
@ -315,6 +328,71 @@ class PCB_PrintOptions(VariantOptions):
|
|||
def get_targets(self, out_dir):
|
||||
return [self._parent.expand_filename(out_dir, self.output)]
|
||||
|
||||
def clear_edge_cuts(self):
|
||||
tmp_layer = GS.board.GetLayerID(GS.work_layer)
|
||||
edge = GS.board.GetLayerID('Edge.Cuts')
|
||||
moved = []
|
||||
for g in GS.board.GetDrawings():
|
||||
if g.GetLayer() == edge:
|
||||
g.SetLayer(tmp_layer)
|
||||
moved.append(g)
|
||||
for m in GS.get_modules():
|
||||
for gi in m.GraphicalItems():
|
||||
if gi.GetLayer() == edge:
|
||||
gi.SetLayer(tmp_layer)
|
||||
moved.append(gi)
|
||||
self.moved_items = moved
|
||||
self.edge_layer = edge
|
||||
|
||||
def restore_edge_cuts(self):
|
||||
for g in self.moved_items:
|
||||
g.SetLayer(self.edge_layer)
|
||||
|
||||
def plot_frame_ki6(self, pc, po, p):
|
||||
""" KiCad 6 can plot the frame because it loads the worksheet format """
|
||||
self.clear_edge_cuts()
|
||||
po.SetPlotFrameRef(True)
|
||||
po.SetScale(1.0)
|
||||
po.SetNegative(False)
|
||||
pc.SetLayer(self.edge_layer)
|
||||
pc.OpenPlotfile('frame', PLOT_FORMAT_PDF, p.sheet)
|
||||
pc.PlotLayer()
|
||||
self.restore_edge_cuts()
|
||||
|
||||
def plot_frame_ki5(self, dir_name):
|
||||
""" KiCad 5 crashes if we try to print the frame.
|
||||
So we print a frame using pcbnew_do export.
|
||||
We use SVG output to then generate a vectorized PDF. """
|
||||
output = os.path.join(dir_name, GS.pcb_basename+"-frame.svg")
|
||||
check_script(CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, '1.6.7')
|
||||
# Move all the drawings away
|
||||
# KiCad 5 always prints Edge.Cuts, so we make it empty
|
||||
self.clear_edge_cuts()
|
||||
# Save the PCB
|
||||
pcb_name, pcb_dir = self.save_tmp_dir_board('pcb_print')
|
||||
# Restore the layer
|
||||
self.restore_edge_cuts()
|
||||
# Output file name
|
||||
cmd = [CMD_PCBNEW_PRINT_LAYERS, 'export', '--output_name', output, '--monochrome', '--svg',
|
||||
pcb_name, dir_name, 'Edge.Cuts']
|
||||
cmd, video_remove = add_extra_options(cmd)
|
||||
# Execute it
|
||||
ret = exec_with_retry(cmd)
|
||||
# Remove the temporal PCB
|
||||
logger.debug('Removing temporal PCB used for frame `{}`'.format(pcb_dir))
|
||||
rmtree(pcb_dir)
|
||||
if ret:
|
||||
logger.error(CMD_PCBNEW_PRINT_LAYERS+' returned %d', ret)
|
||||
exit(PDF_PCB_PRINT)
|
||||
if video_remove:
|
||||
video_name = os.path.join(self.expand_filename_pcb(GS.out_dir), 'pcbnew_export_screencast.ogv')
|
||||
if os.path.isfile(video_name):
|
||||
os.remove(video_name)
|
||||
patch_svg_file(output, remove_bkg=True)
|
||||
# Note: rsvg-convert uses 90 dpi but KiCad (and the docs I found) says SVG pt is 72 dpi
|
||||
cmd = ['rsvg-convert', '-d', '72', '-p', '72', '-f', 'pdf', '-o', output.replace('.svg', '.pdf'), output]
|
||||
_run_command(cmd)
|
||||
|
||||
def generate_output(self, output):
|
||||
temp_dir = mkdtemp(prefix='tmp-kibot-pcb_print-')
|
||||
logger.debug('- Temporal dir: {}'.format(temp_dir))
|
||||
|
|
@ -350,22 +428,19 @@ class PCB_PrintOptions(VariantOptions):
|
|||
pc.OpenPlotfile(la.suffix, PLOT_FORMAT_PDF, p.sheet)
|
||||
pc.PlotLayer()
|
||||
# 2) Plot the frame using an empty layer and 1.0 scale
|
||||
if self.plot_sheet_reference and GS.ki6():
|
||||
if self.plot_sheet_reference:
|
||||
logger.debug('- Plotting the frame')
|
||||
po.SetPlotFrameRef(True)
|
||||
po.SetScale(1.0)
|
||||
po.SetNegative(False)
|
||||
# TODO: Any better option?
|
||||
pc.SetLayer(GS.board.GetLayerID(GS.work_layer))
|
||||
pc.OpenPlotfile('frame', PLOT_FORMAT_PDF, p.sheet)
|
||||
pc.PlotLayer()
|
||||
if GS.ki6():
|
||||
self.plot_frame_ki6(pc, po, p)
|
||||
else:
|
||||
self.plot_frame_ki5(temp_dir)
|
||||
pc.ClosePlot()
|
||||
# 3) Apply the colors to the layer PDFs
|
||||
filelist = []
|
||||
for la in p.layers:
|
||||
colorize_layer(la.suffix, la.color, p.monochrome, filelist, temp_dir, p.black_holes)
|
||||
# 4) Apply color to the frame
|
||||
if self.plot_sheet_reference and GS.ki6():
|
||||
if self.plot_sheet_reference:
|
||||
color = p.sheet_reference_color if p.sheet_reference_color else self._color_theme.pcb_frame
|
||||
colorize_layer('frame', color, p.monochrome, filelist, temp_dir)
|
||||
# 5) Stack all layers in one file
|
||||
|
|
@ -390,8 +465,7 @@ class PCB_PrintOptions(VariantOptions):
|
|||
@output_class
|
||||
class PCB_Print(BaseOutput): # noqa: F821
|
||||
""" PCB Print
|
||||
Prints the PCB using a mechanism that is more flexible than `pdf_pcb_print`.
|
||||
Note that it doesn't support the frame/title block for KiCad 5. """
|
||||
Prints the PCB using a mechanism that is more flexible than `pdf_pcb_print`. """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
with document:
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
import os
|
||||
import re
|
||||
from .gs import GS
|
||||
from .out_any_pcb_print import Any_PCB_PrintOptions
|
||||
from .error import KiPlotConfigurationError
|
||||
from .kicad.patch_svg import patch_svg_file
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from .layer import Layer
|
||||
from . import log
|
||||
|
|
@ -34,15 +34,7 @@ class SVG_PCB_PrintOptions(Any_PCB_PrintOptions):
|
|||
o = self._parent
|
||||
out_files = o.get_targets(o.expand_dirname(os.path.join(GS.out_dir, o.dir)))
|
||||
for file in out_files:
|
||||
logger.debug('Patching SVG file `{}`'.format(file))
|
||||
with open(file, 'rt') as f:
|
||||
text = f.read()
|
||||
text = re.sub(r'<svg (.*) width="(.*)" height="(.*)" viewBox="(\S+) (\S+) (\S+) (\S+)"',
|
||||
r'<svg \1 width="\3" height="\2" viewBox="\4 \5 \7 \6"', text)
|
||||
text = re.sub(r'<rect x="(\S+)" y="(\S+)" width="(\S+)" height="(\S+)"',
|
||||
r'<rect x="\1" y="\2" width="\4" height="\3"', text)
|
||||
with open(file, 'wt') as f:
|
||||
f.write(text)
|
||||
patch_svg_file(file)
|
||||
|
||||
|
||||
@output_class
|
||||
|
|
|
|||
|
|
@ -28,16 +28,17 @@ outputs:
|
|||
- layer: F.Fab
|
||||
plot_footprint_refs: false
|
||||
plot_footprint_values: false
|
||||
- layer: User.Eco1
|
||||
- layer: Dwgs.User
|
||||
- mirror: true
|
||||
layers:
|
||||
- layer: B.Fab
|
||||
color: "#000080"
|
||||
- layer: Edge.Cuts
|
||||
color: "#FF8000"
|
||||
- layer: B.Silkscreen
|
||||
- layer: B.SilkS
|
||||
color: "#626262"
|
||||
- layer: B.Paste
|
||||
color: "#FF8A8A"
|
||||
- layer: B.Cu
|
||||
color: "#B3FFB3"
|
||||
- layer: Dwgs.User
|
||||
|
|
|
|||
Loading…
Reference in New Issue