Added variants to all the PCB plot outputs.
Tested for gerbers that are currently excluding pads from *.Paste.
This commit is contained in:
parent
26ee971e26
commit
c56af11007
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 """
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
Loading…
Reference in New Issue