Added colored pads and vias to pcb_print

- The vias are more "realistic", we avoid showing holes on layers that
  aren't drilled, but we mark the via. So you know were is.
This commit is contained in:
Salvador E. Tropea 2022-04-13 13:51:03 -03:00
parent d84dec153d
commit 9d606fa53b
4 changed files with 180 additions and 6 deletions

View File

@ -1538,6 +1538,8 @@ Next time you need this list just use an alias, like this:
- `color_theme`: [string='_builtin_classic'] Selects the color theme. Only applies to KiCad 6.
To use the KiCad 6 default colors select `_builtin_default`.
Usually user colors are stored as `user`, but you can give it another name.
- `colored_pads`: [boolean=true] Plot through-hole in a different color. Like KiCad GUI does.
- `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.
- `drill_marks`: [string='full'] What to use to indicate the drill places, can be none, small or full (for real scale).
@ -1549,6 +1551,7 @@ Next time you need this list just use an alias, like this:
- `hide_excluded`: [boolean=false] Hide components in the Fab layer that are marked as excluded by a variant.
- `output`: [string='%f-%i%I%v.%x'] Filename for the output (%i=assembly, %x=pdf)/(%i=assembly_page_NN, %x=svg). Affected by global options.
- *output_name*: Alias for output.
- `pad_color`: [string=''] Color used for `colored_pads`.
- `pages`: [list(dict)] List of pages to include in the output document.
Each page contains one or more layers of the PCB.
* Valid keys:
@ -1580,6 +1583,7 @@ Next time you need this list just use an alias, like this:
- `title`: [string=''] Text used to replace the sheet title. %VALUE expansions are allowed.
If it starts with `+` the text is concatenated.
- `variant`: [string=''] Board variant to apply.
- `via_color`: [string=''] Color used for `colored_vias`.
- `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.

View File

@ -964,6 +964,10 @@ outputs:
# To use the KiCad 6 default colors select `_builtin_default`.
# Usually user colors are stored as `user`, but you can give it another name
color_theme: '_builtin_classic'
# [boolean=true] Plot through-hole in a different color. Like KiCad GUI does
colored_pads: true
# [boolean=true] Plot vias in a different color. Like KiCad GUI does
colored_vias: true
# [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'
@ -981,6 +985,8 @@ outputs:
# [string='%f-%i%I%v.%x'] Filename for the output (%i=assembly, %x=pdf)/(%i=assembly_page_NN, %x=svg). Affected by global options
output: '%f-%i%I%v.%x'
# `output_name` is an alias for `output`
# [string=''] Color used for `colored_pads`
pad_color: ''
# [list(dict)] List of pages to include in the output document.
# Each page contains one or more layers of the PCB
pages:
@ -1036,6 +1042,8 @@ outputs:
title: ''
# [string=''] Board variant to apply
variant: ''
# [string=''] Color used for `colored_vias`
via_color: ''
# PcbDraw - Beautiful 2D PCB render:
# Uses configurable colors.
# Can also render the components if the 2D models are available

View File

@ -26,6 +26,11 @@ KI6_KI5 = {'b_adhesive': 'b_adhes',
'user_eco2': 'eco2_user',
'b_courtyard': 'b_crtyd',
'f_courtyard': 'f_crtyd'}
BOARD_COLORS = {'worksheet': 'pcb_frame',
'pad_through_hole': 'pad_through_hole',
'via_through': 'via_through',
'via_blind_buried': 'via_blind_buried',
'via_micro': 'via_micro'}
CACHE = {}
@ -33,6 +38,8 @@ class KiCadColors(object):
def __init__(self):
self.layer_id2color = {}
self.pcb_frame = "#480000"
self.pad_through_hole = "#C2C200"
self.via_through = "#C2C2C2"
def parse_color(val):
@ -95,8 +102,9 @@ def load_color_theme(name):
logger.warning(W_WRONGCOLOR+"The `{}` theme doesn't define a color for the {} layer".format(name, c_name_ori))
if extra_debug:
logger.debug('- Color for layer {} ({}): {}'.format(c_name_ori, id, cl[id]))
# Title block and frame color
if 'worksheet' in board:
c.pcb_frame = parse_color(board['worksheet'])
# Other colors (Title block and frame color, vias, etc.)
for color, member in BOARD_COLORS.items():
if color in board:
setattr(c, member, parse_color(board[color]))
CACHE[fn] = c
return c

View File

@ -8,7 +8,7 @@
# Note: Original code released as Public Domain
import os
import subprocess
from pcbnew import PLOT_CONTROLLER, FromMM, PLOT_FORMAT_SVG
from pcbnew import PLOT_CONTROLLER, FromMM, PLOT_FORMAT_SVG, F_Cu, B_Cu, wxSize, IsCopperLayer
from shutil import rmtree, which
from tempfile import mkdtemp
from .svgutils.transform import fromstring
@ -28,10 +28,12 @@ from . import log
logger = log.get_logger()
SVG2PDF = 'rsvg-convert'
PDF2PS = 'pdf2ps'
VIATYPE_THROUGH = 3
VIATYPE_BLIND_BURIED = 2
VIATYPE_MICROVIA = 1
# - Use PyPDF2 for pdfunite
# - Implement pad, vias, etc colors
# - Analyze KiCad 6 long delay
# - Manually draw the frame
@ -272,6 +274,10 @@ class PagesOptions(Optionable):
class PCB_PrintOptions(VariantOptions):
# Mappings to KiCad config values. They should be the same used in drill_marks.py
_drill_marks_map = {'none': 0, 'small': 1, 'full': 2}
_pad_colors = {'pad_color': 'pad_through_hole',
'via_color': 'via_through',
'micro_via_color': 'via_micro',
'blind_via_color': 'via_blind_buried'}
def __init__(self):
with document:
@ -304,6 +310,18 @@ class PCB_PrintOptions(VariantOptions):
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 """
self.colored_pads = True
""" Plot through-hole in a different color. Like KiCad GUI does """
self.pad_color = ''
""" Color used for `colored_pads` """
self.colored_vias = True
""" Plot vias in a different color. Like KiCad GUI does """
self.via_color = ''
""" Color used for through-hole `colored_vias` """
self.micro_via_color = ''
""" Color used for micro `colored_vias` """
self.blind_via_color = ''
""" Color used for blind/buried `colored_vias` """
super().__init__()
self._expand_id = 'assembly'
@ -335,6 +353,11 @@ class PCB_PrintOptions(VariantOptions):
la.color = "#000000"
self._drill_marks = PCB_PrintOptions._drill_marks_map[self._drill_marks]
self._expand_ext = self.format.lower()
for member, color in self._pad_colors.items():
if getattr(self, member):
self.validate_color(member)
else:
setattr(self, member, getattr(self._color_theme, color))
def filter_components(self):
if not self._comps:
@ -426,6 +449,129 @@ class PCB_PrintOptions(VariantOptions):
os.remove(video_name)
patch_svg_file(output, remove_bkg=True)
def plot_pads(self, la, pc, p, filelist):
id = la._id
logger.debug('- Plotting pads for layer {} ({})'.format(la.layer, id))
# Make invisible anything but through-hole pads
tmp_layer = GS.board.GetLayerID(GS.work_layer)
moved = []
resized = []
vias = []
size_0 = wxSize(0, 0)
for m in GS.get_modules():
for gi in m.GraphicalItems():
if gi.GetLayer() == id:
gi.SetLayer(tmp_layer)
moved.append(gi)
for pad in m.Pads():
dr = pad.GetDrillSize()
if dr.x:
continue
resized.append((pad, pad.GetSize()))
pad.SetSize(size_0)
for e in GS.board.GetDrawings():
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
moved.append(e)
for e in list(GS.board.Zones()):
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
moved.append(e)
via_type = 'VIA' if GS.ki5() else 'PCB_VIA'
for e in GS.board.GetTracks():
if e.GetClass() == via_type:
vias.append((e, e.GetDrill(), e.GetWidth()))
e.SetDrill(0)
e.SetWidth(0)
else:
e.SetLayer(tmp_layer)
moved.append(e)
# Plot the layer
# pc.SetLayer(id) already selected
suffix = la.suffix+'_pads'
pc.OpenPlotfile(suffix, PLOT_FORMAT_SVG, p.sheet)
pc.PlotLayer()
# Restore everything
for e in moved:
e.SetLayer(id)
for (pad, size) in resized:
pad.SetSize(size)
for (via, drill, width) in vias:
via.SetDrill(drill)
via.SetWidth(width)
# Add it to the list
filelist.append((GS.pcb_basename+"-"+suffix+".svg", self.pad_color))
def plot_vias(self, la, pc, p, filelist, via_t, via_c):
id = la._id
logger.debug('- Plotting vias for layer {} ({})'.format(la.layer, id))
# Make invisible anything but vias
tmp_layer = GS.board.GetLayerID(GS.work_layer)
moved = []
resized = []
vias = []
size_0 = wxSize(0, 0)
for m in GS.get_modules():
for gi in m.GraphicalItems():
if gi.GetLayer() == id:
gi.SetLayer(tmp_layer)
moved.append(gi)
for pad in m.Pads():
resized.append((pad, pad.GetSize()))
pad.SetSize(size_0)
for e in GS.board.GetDrawings():
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
moved.append(e)
for e in list(GS.board.Zones()):
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
moved.append(e)
via_type = 'VIA' if GS.ki5() else 'PCB_VIA'
for e in GS.board.GetTracks():
if e.GetClass() == via_type:
if e.GetViaType() == via_t:
# Include it, but ...
if not e.IsOnLayer(id):
# This is a via that doesn't drill this layer
# Lamentably KiCad will draw a drill here
# So we create a "patch" for the hole
top = e.TopLayer()
bottom = e.BottomLayer()
w = e.GetWidth()
d = e.GetDrill()
vias.append((e, d, w, top, bottom))
e.SetWidth(d)
e.SetDrill(1)
e.SetTopLayer(F_Cu)
e.SetBottomLayer(B_Cu)
else:
top = e.TopLayer()
bottom = e.BottomLayer()
w = e.GetWidth()
d = e.GetDrill()
vias.append((e, d, w, top, bottom))
e.SetWidth(0)
else:
e.SetLayer(tmp_layer)
moved.append(e)
# Plot the layer
suffix = la.suffix+'_vias_'+str(via_t)
pc.OpenPlotfile(suffix, PLOT_FORMAT_SVG, p.sheet)
pc.PlotLayer()
# Restore everything
for e in moved:
e.SetLayer(id)
for (pad, size) in resized:
pad.SetSize(size)
for (via, drill, width, top, bottom) in vias:
via.SetDrill(drill)
via.SetWidth(width)
via.SetTopLayer(top)
via.SetBottomLayer(bottom)
# Add it to the list
filelist.append((GS.pcb_basename+"-"+suffix+".svg", via_c))
def generate_output(self, output):
if self.format != 'SVG' and which(SVG2PDF) is None:
logger.error('`{}` not installed. Install `librsvg2-bin` or equivalent'.format(SVG2PDF))
@ -445,7 +591,6 @@ class PCB_PrintOptions(VariantOptions):
po.SetExcludeEdgeLayer(True) # We plot it separately
po.SetUseAuxOrigin(False)
po.SetAutoScale(False)
po.SetDrillMarksType(self._drill_marks)
# Generate the output
pages = []
for n, p in enumerate(self.pages):
@ -466,10 +611,19 @@ class PCB_PrintOptions(VariantOptions):
po.SetPlotReference(la.plot_footprint_refs)
po.SetPlotValue(la.plot_footprint_values)
po.SetPlotInvisibleText(la.force_plot_invisible_refs_vals)
# Avoid holes on non-copper layers
po.SetDrillMarksType(self._drill_marks if IsCopperLayer(id) else 0)
pc.SetLayer(id)
pc.OpenPlotfile(la.suffix, PLOT_FORMAT_SVG, p.sheet)
pc.PlotLayer()
filelist.append((GS.pcb_basename+"-"+la.suffix+".svg", la.color))
if id >= F_Cu and id <= B_Cu:
if self.colored_pads:
self.plot_pads(la, pc, p, filelist)
if self.colored_vias:
self.plot_vias(la, pc, p, filelist, VIATYPE_THROUGH, self.via_color)
self.plot_vias(la, pc, p, filelist, VIATYPE_BLIND_BURIED, self.blind_via_color)
self.plot_vias(la, pc, p, filelist, VIATYPE_MICROVIA, self.micro_via_color)
# 2) Plot the frame using an empty layer and 1.0 scale
if self.plot_sheet_reference:
logger.debug('- Plotting the frame')