Added variants to all the PCB plot outputs.

Tested for gerbers that are currently excluding pads from *.Paste.
This commit is contained in:
Salvador E. Tropea 2020-09-03 11:11:03 -03:00
parent 26ee971e26
commit c56af11007
7 changed files with 176 additions and 10 deletions

View File

@ -48,6 +48,10 @@ AUTO_SCALE = 0
# Internal filter names
IFILL_MECHANICAL = '_mechanical'
# KiCad 5 GUI values for the attribute
UI_THT = 0
UI_SMD = 1
UI_VIRTUAL = 2
# Supported values for "do not fit"
DNF = {

View File

@ -6,13 +6,17 @@
# Project: KiBot (formerly KiPlot)
# Adapted from: https://github.com/johnbeard/kiplot
import os
from pcbnew import (GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer)
from pcbnew import GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer, LSET
from .out_base import (BaseOutput)
from .error import (PlotError, KiPlotConfigurationError)
from .optionable import BaseOptions
from .optionable import BaseOptions, Optionable
from .registrable import RegOutput
from .layer import Layer
from .gs import GS
from .misc import UI_VIRTUAL
from .kiplot import load_sch
from .macros import macros, document # noqa: F401
from .fil_base import BaseFilter, apply_fitted_filter
from . import log
logger = log.get_logger(__name__)
@ -38,8 +42,18 @@ class AnyLayerOptions(BaseOptions):
""" output file name, the default KiCad name if empty """
self.tent_vias = True
""" cover the vias """
self.variant = ''
""" Board variant(s) to apply """
self.dnf_filter = Optionable
""" [string|list(string)=''] Name of the filter to mark components as not fitted.
A short-cut to use for simple cases where a variant is an overkill """
super().__init__()
def config(self):
super().config()
self.variant = RegOutput.check_variant(self.variant)
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
def _configure_plot_ctrl(self, po, output_dir):
logger.debug("Configuring plot controller for output")
po.SetOutputDirectory(output_dir)
@ -70,6 +84,39 @@ class AnyLayerOptions(BaseOptions):
plot_ctrl.SetColorMode(True)
# Apply the variants and filters
exclude = None
if hasattr(self, 'variant') and (self.dnf_filter or self.variant):
load_sch()
# Get the components list from the schematic
comps = GS.sch.get_components()
# Apply the filter
apply_fitted_filter(comps, self.dnf_filter)
# Apply the variant
if self.variant:
# Apply the variant
self.variant.filter(comps)
comps_hash = {c.ref: c for c in comps}
# Remove from solder past layers the filtered components
exclude = LSET()
exclude.addLayer(board.GetLayerID('F.Paste'))
exclude.addLayer(board.GetLayerID('B.Paste'))
old_layers = []
for m in board.GetModules():
ref = m.GetReference()
# logger.debug('Ref {}'.format(ref))
c = comps_hash.get(ref, None)
# logger.debug('Component {}'.format(c))
if (c and not c.fitted) or m.GetAttributes() == UI_VIRTUAL:
# logger.debug('Removing')
old_c_layers = []
for p in m.Pads():
pad_layers = p.GetLayerSet()
old_c_layers.append(pad_layers.FmtHex())
pad_layers.removeLayerSet(exclude)
p.SetLayerSet(pad_layers)
old_layers.append(old_c_layers)
layers = Layer.solve(layers)
# plot every layer in the output
for la in layers:
@ -104,6 +151,18 @@ class AnyLayerOptions(BaseOptions):
if create_job:
jobfile_writer.CreateJobFile(self.expand_filename(output_dir, po.gerber_job_file, 'job', 'gbrjob'))
# Restore the eliminated layers
if exclude:
for m in board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if (c and not c.fitted) or m.GetAttributes() == UI_VIRTUAL:
restore = old_layers.pop(0)
for p in m.Pads():
pad_layers = p.GetLayerSet()
res = restore.pop(0)
pad_layers.ParseHex(res, len(res))
p.SetLayerSet(pad_layers)
def read_vals_from_po(self, po):
# excludeedgelayer

View File

@ -11,16 +11,13 @@ from pcbnew import (IU_PER_MM, IU_PER_MILS)
from .optionable import BaseOptions, Optionable
from .registrable import RegOutput
from .gs import GS
from .misc import UI_SMD, UI_VIRTUAL
from .kiplot import load_sch
from .macros import macros, document, output_class # noqa: F401
from .fil_base import BaseFilter, apply_fitted_filter
from . import log
logger = log.get_logger(__name__)
# KiCad 5 GUI values for the attribute
UI_THT = 0
UI_SMD = 1
UI_VIRTUAL = 2
class PositionOptions(BaseOptions):
@ -167,8 +164,6 @@ class PositionOptions(BaseOptions):
# Apply any filter or variant data
if comps:
c = comps_hash.get(ref, None)
if c:
logger.debug("{} {} {}".format(ref, c.fitted, c.in_bom))
if c and not c.fitted:
continue
# If passed check the position options

View File

@ -138,6 +138,7 @@
(fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05))
(fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05))
(fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05))
(fp_circle (center 0 0) (end 0.4 0) (layer F.Adhes) (width 0.1))
(fp_text user %R (at 0 0) (layer F.Fab)
(effects (font (size 0.5 0.5) (thickness 0.08)))
)

View File

@ -19,6 +19,27 @@ from kibot.misc import (PLOT_ERROR)
GERBER_DIR = 'gerberdir'
ALL_LAYERS = ['B_Adhes',
'B_CrtYd',
'B_Cu',
'B_Fab',
'B_Mask',
'B_Paste',
'B_SilkS',
'Cmts_User',
'Dwgs_User',
'Eco1_User',
'Eco2_User',
'Edge_Cuts',
'F_Adhes',
'F_CrtYd',
'F_Cu',
'F_Fab',
'F_Mask',
'F_Paste',
'F_SilkS',
'Margin',
]
def test_gerber_2layer():
@ -58,3 +79,40 @@ def test_gerber_inner_wrong():
ctx.run(PLOT_ERROR)
assert ctx.search_err('is not valid for this board')
ctx.clean_up()
def compose_fname(dir, prefix, layer, suffix, ext='gbr'):
return os.path.join(dir, prefix+'-'+layer+suffix+'.'+ext)
def check_layers_exist(ctx, dir, prefix, layers, suffix):
for layer in layers:
ctx.expect_out_file(compose_fname(dir, prefix, layer, suffix))
ctx.expect_out_file(compose_fname(dir, prefix, 'job', suffix, 'gbrjob'))
def check_components(ctx, dir, prefix, layer, suffix, exclude, include):
fname = compose_fname(dir, prefix, layer, suffix)
inc = [r'%TO\.C,{}\*%'.format(v) for v in include]
ctx.search_in_file(fname, inc)
exc = [r'%TO\.C,{}\*%'.format(v) for v in exclude]
ctx.search_not_in_file(fname, exc)
def test_gerber_variant_1():
prj = 'kibom-variant_3'
ctx = context.TestContext('test_gerber_variant_1', prj, 'gerber_variant_1', GERBER_DIR)
ctx.run()
# C1 is virtual, not included for all cases
# R3 is a component added to the PCB, included in all cases
# variant: default directory: gerber components: R1, R2 and R3
check_layers_exist(ctx, 'gerber', prj, ALL_LAYERS, '')
check_components(ctx, 'gerber', prj, 'F_Paste', '', ['C1', 'C2'], ['R1', 'R2', 'R3'])
# variant: production directory: production components: R1, R2, R3 and C2
check_layers_exist(ctx, 'production', prj, ALL_LAYERS, '_(production)')
check_components(ctx, 'production', prj, 'F_Paste', '_(production)', ['C1'], ['R1', 'R2', 'R3', 'C2'])
# variant: test directory: test components: R1, R3 and C2
check_layers_exist(ctx, 'test', prj, ALL_LAYERS, '_(test)')
check_components(ctx, 'test', prj, 'F_Paste', '_(test)', ['C1', 'R2'], ['R1', 'R3', 'C2'])
ctx.clean_up()

View File

@ -304,9 +304,11 @@ class TestContext(object):
with open(self.get_out_path(file)) as f:
txt = f.read()
for t in texts:
logging.debug('- r"'+t+'"')
msg = '- r"'+t+'"'
m = re.search(t, txt, re.MULTILINE)
assert m is None
assert m is None, msg
logging.debug(msg+' OK')
# logging.debug(' '+m.group(0))
def compare_image(self, image, reference=None, diff='diff.png', ref_out_dir=False):
""" For images and single page PDFs """

View File

@ -0,0 +1,47 @@
# Example KiBot config file
kibot:
version: 1
variants:
- name: 'production'
comment: 'Production variant'
type: ibom
file_id: '_(production)'
variants_blacklist: T2
- name: 'test'
comment: 'Test variant'
type: ibom
file_id: '_(test)'
variants_blacklist: T1
- name: 'default'
comment: 'Default variant'
type: ibom
variants_blacklist: T2,T3
outputs:
- name: 'gerber'
comment: "Gerber"
type: gerber
dir: gerber
layers: all
options:
variant: default
- name: 'gerber_production'
comment: "Gerber for production"
type: gerber
dir: production
layers: all
options:
variant: production
- name: 'gerber_test'
comment: "Gerber for test"
type: gerber
dir: test
layers: all
options:
variant: test