Added more coherence to the variants/filters.

- Now they behave more coherently across outputs.
- Component status is explicitly reset, so having a variant + filter
  behaves in a more consistent way. Even when this isn't the
  recommended use.
- Virtual components are handled by the exclude filter.
- Moved code to modify the PCB to VariantOptions.
- Added a real virtual component to the tests, not a ridiculous case.
This commit is contained in:
Salvador E. Tropea 2020-09-08 20:01:16 -03:00
parent 23e46df1c5
commit b03b56ae5d
22 changed files with 238 additions and 221 deletions

View File

@ -301,7 +301,7 @@ def group_components(cfg, components):
groups = []
# Iterate through each component, and test whether a group for these already exists
for c in components:
if not c.in_bom: # Skip components marked as excluded from BoM
if not c.included: # Skip components marked as excluded from BoM
continue
# Cache the value used to sort
if c.ref_prefix in RLC_PREFIX and c.value.lower() not in DNF:

View File

@ -65,34 +65,35 @@ class NotFilter(Registrable):
return not self._filter.filter(comp)
def apply_in_bom_filter(comps, filter, reset=True):
def reset_filters(comps):
for c in comps:
c.included = True
c.fitted = True
c.fixed = False
def apply_exclude_filter(comps, filter):
if filter:
logger.debug('Applying filter `{}` to in_bom'.format(filter.name))
logger.debug('Applying filter `{}` to exclude'.format(filter.name))
for c in comps:
c.in_bom = filter.filter(c)
elif reset: # Reset the field
for c in comps:
c.in_bom = True
if c.included:
c.included = filter.filter(c)
def apply_fitted_filter(comps, filter, reset=True):
def apply_fitted_filter(comps, filter):
if filter:
logger.debug('Applying filter `{}` to fitted'.format(filter.name))
for c in comps:
c.fitted = filter.filter(c)
elif reset: # Reset the field
for c in comps:
c.fitted = True
if c.fitted:
c.fitted = filter.filter(c)
def apply_fixed_filter(comps, filter, reset=True):
def apply_fixed_filter(comps, filter):
if filter:
logger.debug('Applying filter `{}` to fixed'.format(filter.name))
for c in comps:
c.fixed = filter.filter(c)
elif reset: # Reset the field
for c in comps:
c.fixed = False
if not c.fixed:
c.fixed = filter.filter(c)
class BaseFilter(RegFilter):
@ -120,6 +121,7 @@ class BaseFilter(RegFilter):
o_tree['comment'] = 'Internal default mechanical filter'
o_tree['exclude_all_hash_ref'] = True
o_tree['exclude_any'] = DEFAULT_EXCLUDE
o_tree['exclude_virtual'] = True
logger.debug('Creating internal filter: '+str(o_tree))
return o_tree

View File

@ -783,7 +783,8 @@ class SchematicComponent(object):
- Solded: only if True
- BoM normal: only if True
- BoM DNF: only if False
- in_bom: equivalent to 'Exclude from BoM' but inverted
- included: related to 'Exclude from BoM' but inverted,
but also applied to other situations.
- Solded: doesn't affected
- BoM normal: only if True
- BoM DNF: only if True (and fitted is False)
@ -801,7 +802,7 @@ class SchematicComponent(object):
self.desc = ''
# Will be computed
self.fitted = True
self.in_bom = True
self.included = True
self.fixed = False
# KiCad 5 PCB flags (mutually exclusive)
self.smd = False
@ -996,7 +997,7 @@ class SchematicComponent(object):
def write(self, f):
# Fake lib to reflect fitted status
lib = 'y' if self.fitted else 'n'
lib = 'y' if self.fitted or not self.included else 'n'
# Fake name using cache style
name = '{}:{}_{}'.format(lib, self.lib, self.name)
f.write('$Comp\n')

View File

@ -6,12 +6,11 @@
# Project: KiBot (formerly KiPlot)
# Adapted from: https://github.com/johnbeard/kiplot
import os
from pcbnew import GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer, LSET, wxPoint, EDGE_MODULE
from pcbnew import GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer
from .out_base import (BaseOutput)
from .error import (PlotError, KiPlotConfigurationError)
from .layer import Layer
from .gs import GS
from .misc import UI_VIRTUAL, Rect
from .out_base import VariantOptions
from .macros import macros, document # noqa: F401
from . import log
@ -56,113 +55,17 @@ class AnyLayerOptions(VariantOptions):
# We'll come back to this on a per-layer basis
po.SetSkipPlotNPTH_Pads(False)
@staticmethod
def cross_module(m, rect, layer):
seg1 = EDGE_MODULE(m)
m.Add(seg1)
seg1.SetWidth(120000)
seg1.SetStart(wxPoint(rect.x1, rect.y1))
seg1.SetEnd(wxPoint(rect.x2, rect.y2))
seg1.SetLayer(layer)
seg2 = EDGE_MODULE(m)
m.Add(seg2)
seg2.SetWidth(120000)
seg2.SetStart(wxPoint(rect.x1, rect.y2))
seg2.SetEnd(wxPoint(rect.x2, rect.y1))
seg2.SetLayer(layer)
return [seg1, seg2]
def filter_components(self, board):
""" Apply the variants and filters """
if not self._comps:
return None
comps_hash = self.get_refs_hash()
# Remove from solder past layers the filtered components
exclude = LSET()
exclude.addLayer(board.GetLayerID('F.Paste'))
exclude.addLayer(board.GetLayerID('B.Paste'))
old_layers = []
fadhes = board.GetLayerID('F.Adhes')
badhes = board.GetLayerID('B.Adhes')
old_fadhes = []
old_badhes = []
ffab = board.GetLayerID('F.Fab')
bfab = board.GetLayerID('B.Fab')
extra_ffab_lines = []
extra_bfab_lines = []
for m in board.GetModules():
ref = m.GetReference()
# Rectangle containing the drawings, no text
frect = Rect()
brect = Rect()
c = comps_hash.get(ref, None)
if (c and not c.fitted) or m.GetAttributes() == UI_VIRTUAL:
# Remove all pads from *.Paste
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)
# Remove any graphical item in the *.Adhes layers
# Also: meassure the *.Fab drawings size
for gi in m.GraphicalItems():
l_gi = gi.GetLayer()
if l_gi == fadhes:
gi.SetLayer(-1)
old_fadhes.append(gi)
if l_gi == badhes:
gi.SetLayer(-1)
old_badhes.append(gi)
if gi.GetClass() == 'MGRAPHIC':
if l_gi == ffab:
frect.Union(gi.GetBoundingBox().getWxRect())
if l_gi == bfab:
brect.Union(gi.GetBoundingBox().getWxRect())
# Cross the graphics in *.Fab
if frect.x1 is not None:
extra_ffab_lines.append(self.cross_module(m, frect, ffab))
else:
extra_ffab_lines.append(None)
if brect.x1 is not None:
extra_bfab_lines.append(self.cross_module(m, brect, bfab))
else:
extra_bfab_lines.append(None)
# Store the data to undo the above actions
self.comps_hash = comps_hash
self.old_layers = old_layers
self.old_fadhes = old_fadhes
self.old_badhes = old_badhes
self.extra_ffab_lines = extra_ffab_lines
self.extra_bfab_lines = extra_bfab_lines
return exclude
self.comps_hash = self.get_refs_hash()
self.cross_modules(board, self.comps_hash)
return self.remove_paste_and_glue(board, self.comps_hash)
def unfilter_components(self, board):
for m in board.GetModules():
ref = m.GetReference()
c = self.comps_hash.get(ref, None)
if (c and not c.fitted) or m.GetAttributes() == UI_VIRTUAL:
restore = self.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)
restore = self.extra_ffab_lines.pop(0)
if restore:
for line in restore:
m.Remove(line)
restore = self.extra_bfab_lines.pop(0)
if restore:
for line in restore:
m.Remove(line)
fadhes = board.GetLayerID('F.Adhes')
for gi in self.old_fadhes:
gi.SetLayer(fadhes)
badhes = board.GetLayerID('B.Adhes')
for gi in self.old_badhes:
gi.SetLayer(badhes)
self.uncross_modules(board, self.comps_hash)
self.restore_paste_and_glue(board, self.comps_hash)
def run(self, output_dir, board, layers):
super().run(output_dir, board)

View File

@ -3,11 +3,13 @@
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from pcbnew import EDGE_MODULE, wxPoint, LSET
from .gs import GS
from .misc import Rect
from .kiplot import load_sch
from .registrable import RegOutput
from .optionable import Optionable, BaseOptions
from .fil_base import BaseFilter, apply_fitted_filter
from .fil_base import BaseFilter, apply_fitted_filter, reset_filters
from .macros import macros, document # noqa: F401
from . import log
@ -92,14 +94,143 @@ class VariantOptions(BaseOptions):
return {c.ref: c for c in self._comps}
def get_fitted_refs(self):
""" List of fitted and included components """
if not self._comps:
return []
return [c.ref for c in self._comps if c.fitted]
return [c.ref for c in self._comps if c.fitted and c.included]
def get_not_fitted_refs(self):
""" List of 'not fitted' components, also includes 'not included' """
if not self._comps:
return []
return [c.ref for c in self._comps if not c.fitted]
return [c.ref for c in self._comps if not c.fitted or not c.included]
@staticmethod
def cross_module(m, rect, layer):
""" Draw a cross over a module.
The rect is a Rect object with the size.
The layer is which layer id will be used. """
seg1 = EDGE_MODULE(m)
seg1.SetWidth(120000)
seg1.SetStart(wxPoint(rect.x1, rect.y1))
seg1.SetEnd(wxPoint(rect.x2, rect.y2))
seg1.SetLayer(layer)
seg1.SetLocalCoord() # Update the local coordinates
m.Add(seg1)
seg2 = EDGE_MODULE(m)
seg2.SetWidth(120000)
seg2.SetStart(wxPoint(rect.x1, rect.y2))
seg2.SetEnd(wxPoint(rect.x2, rect.y1))
seg2.SetLayer(layer)
seg2.SetLocalCoord() # Update the local coordinates
m.Add(seg2)
return [seg1, seg2]
def cross_modules(self, board, comps_hash):
""" Draw a cross in all 'not fitted' modules using *.Fab layer """
# Cross the affected components
ffab = board.GetLayerID('F.Fab')
bfab = board.GetLayerID('B.Fab')
extra_ffab_lines = []
extra_bfab_lines = []
for m in board.GetModules():
ref = m.GetReference()
# Rectangle containing the drawings, no text
frect = Rect()
brect = Rect()
c = comps_hash.get(ref, None)
if c and c.included and not c.fitted:
# Meassure the component BBox (only graphics)
for gi in m.GraphicalItems():
if gi.GetClass() == 'MGRAPHIC':
l_gi = gi.GetLayer()
if l_gi == ffab:
frect.Union(gi.GetBoundingBox().getWxRect())
if l_gi == bfab:
brect.Union(gi.GetBoundingBox().getWxRect())
# Cross the graphics in *.Fab
if frect.x1 is not None:
extra_ffab_lines.append(self.cross_module(m, frect, ffab))
else:
extra_ffab_lines.append(None)
if brect.x1 is not None:
extra_bfab_lines.append(self.cross_module(m, brect, bfab))
else:
extra_bfab_lines.append(None)
# Remmember the data used to undo it
self.extra_ffab_lines = extra_ffab_lines
self.extra_bfab_lines = extra_bfab_lines
def uncross_modules(self, board, comps_hash):
""" Undo the crosses in *.Fab layer """
# Undo the drawings
for m in board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if c and c.included and not c.fitted:
restore = self.extra_ffab_lines.pop(0)
if restore:
for line in restore:
m.Remove(line)
restore = self.extra_bfab_lines.pop(0)
if restore:
for line in restore:
m.Remove(line)
def remove_paste_and_glue(self, board, comps_hash):
""" Remove from solder paste layers the filtered components. """
exclude = LSET()
exclude.addLayer(board.GetLayerID('F.Paste'))
exclude.addLayer(board.GetLayerID('B.Paste'))
old_layers = []
fadhes = board.GetLayerID('F.Adhes')
badhes = board.GetLayerID('B.Adhes')
old_fadhes = []
old_badhes = []
for m in board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if c and c.included and not c.fitted:
# Remove all pads from *.Paste
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)
# Remove any graphical item in the *.Adhes layers
for gi in m.GraphicalItems():
l_gi = gi.GetLayer()
if l_gi == fadhes:
gi.SetLayer(-1)
old_fadhes.append(gi)
if l_gi == badhes:
gi.SetLayer(-1)
old_badhes.append(gi)
# Store the data to undo the above actions
self.old_layers = old_layers
self.old_fadhes = old_fadhes
self.old_badhes = old_badhes
self.fadhes = fadhes
self.badhes = badhes
return exclude
def restore_paste_and_glue(self, board, comps_hash):
for m in board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if c and c.included and not c.fitted:
restore = self.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)
for gi in self.old_fadhes:
gi.SetLayer(self.fadhes)
for gi in self.old_badhes:
gi.SetLayer(self.badhes)
def run(self, output_dir, board):
""" Makes the list of components available """
@ -109,6 +240,7 @@ class VariantOptions(BaseOptions):
# Get the components list from the schematic
comps = GS.sch.get_components()
# Apply the filter
reset_filters(comps)
apply_fitted_filter(comps, self.dnf_filter)
# Apply the variant
if self.variant:

View File

@ -17,7 +17,7 @@ from .macros import macros, document, output_class # noqa: F401
from .bom.columnlist import ColumnList, BoMError
from .bom.bom import do_bom
from .var_kibom import KiBoM
from .fil_base import BaseFilter, apply_in_bom_filter, apply_fitted_filter, apply_fixed_filter
from .fil_base import BaseFilter, apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, reset_filters
from . import log
logger = log.get_logger(__name__)
@ -369,7 +369,8 @@ class BoMOptions(BaseOptions):
comps = GS.sch.get_components()
get_board_comps_data(comps)
# Apply all the filters
apply_in_bom_filter(comps, self.exclude_filter)
reset_filters(comps)
apply_exclude_filter(comps, self.exclude_filter)
apply_fitted_filter(comps, self.dnf_filter)
apply_fixed_filter(comps, self.dnc_filter)
# Apply the variant

View File

@ -5,12 +5,11 @@
# Project: KiBot (formerly KiPlot)
import os
from tempfile import NamedTemporaryFile
from pcbnew import EDGE_MODULE, wxPoint
from .pre_base import BasePreFlight
from .error import (KiPlotConfigurationError)
from .gs import (GS)
from .kiplot import check_script, exec_with_retry
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT, Rect, UI_VIRTUAL)
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT)
from .out_base import VariantOptions
from .macros import macros, document, output_class # noqa: F401
from .layer import Layer
@ -28,75 +27,17 @@ class PDF_Pcb_PrintOptions(VariantOptions):
""" {output} """
super().__init__()
@staticmethod
def cross_module(m, rect, layer):
seg1 = EDGE_MODULE(m)
seg1.SetWidth(120000)
seg1.SetStart(wxPoint(rect.x1, rect.y1))
seg1.SetEnd(wxPoint(rect.x2, rect.y2))
seg1.SetLayer(layer)
seg1.SetLocalCoord() # Update the local coordinates
m.Add(seg1)
seg2 = EDGE_MODULE(m)
seg2.SetWidth(120000)
seg2.SetStart(wxPoint(rect.x1, rect.y2))
seg2.SetEnd(wxPoint(rect.x2, rect.y1))
seg2.SetLayer(layer)
seg2.SetLocalCoord() # Update the local coordinates
m.Add(seg2)
return [seg1, seg2]
def filter_components(self, board):
if not self._comps:
return GS.pcb_file
comps_hash = self.get_refs_hash()
# Cross the affected components
ffab = board.GetLayerID('F.Fab')
bfab = board.GetLayerID('B.Fab')
extra_ffab_lines = []
extra_bfab_lines = []
for m in board.GetModules():
ref = m.GetReference()
# Rectangle containing the drawings, no text
frect = Rect()
brect = Rect()
c = comps_hash.get(ref, None)
if (c and not c.fitted) and m.GetAttributes() != UI_VIRTUAL:
# Meassure the component BBox (only graphics)
for gi in m.GraphicalItems():
if gi.GetClass() == 'MGRAPHIC':
l_gi = gi.GetLayer()
if l_gi == ffab:
frect.Union(gi.GetBoundingBox().getWxRect())
if l_gi == bfab:
brect.Union(gi.GetBoundingBox().getWxRect())
# Cross the graphics in *.Fab
if frect.x1 is not None:
extra_ffab_lines.append(self.cross_module(m, frect, ffab))
else:
extra_ffab_lines.append(None)
if brect.x1 is not None:
extra_bfab_lines.append(self.cross_module(m, brect, bfab))
else:
extra_bfab_lines.append(None)
self.cross_modules(board, comps_hash)
# Save the PCB to a temporal file
with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False) as f:
fname = f.name
logger.debug('Storing filtered PCB to `{}`'.format(fname))
GS.board.Save(fname)
# Undo the drawings
for m in GS.board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if (c and not c.fitted) and m.GetAttributes() != UI_VIRTUAL:
restore = extra_ffab_lines.pop(0)
if restore:
for line in restore:
m.Remove(line)
restore = extra_bfab_lines.pop(0)
if restore:
for line in restore:
m.Remove(line)
self.uncross_modules(board, comps_hash)
return fname
def run(self, output_dir, board, layers):

View File

@ -141,7 +141,7 @@ class PositionOptions(VariantOptions):
# Apply any filter or variant data
if comps_hash:
c = comps_hash.get(ref, None)
if c and not c.fitted:
if c and (not c.fitted or not c.included):
continue
# If passed check the position options
if (self.only_smd and m.GetAttributes() == UI_SMD) or \

View File

@ -53,7 +53,7 @@ class STEPOptions(VariantOptions):
for m in GS.board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if c and not c.fitted:
if c and c.included and not c.fitted:
models = m.Models()
rem_m_models = []
while not models.empty():
@ -68,7 +68,7 @@ class STEPOptions(VariantOptions):
for m in GS.board.GetModules():
ref = m.GetReference()
c = comps_hash.get(ref, None)
if c and not c.fitted:
if c and c.included and not c.fitted:
models = m.Models()
restore = rem_models.pop(0)
for model in restore:

View File

@ -5,7 +5,7 @@
# Project: KiBot (formerly KiPlot)
from .registrable import RegVariant
from .optionable import Optionable
from .fil_base import apply_in_bom_filter, apply_fitted_filter, apply_fixed_filter
from .fil_base import apply_exclude_filter, apply_fitted_filter, apply_fixed_filter
from .macros import macros, document # noqa: F401
@ -33,8 +33,8 @@ class BaseVariant(RegVariant):
""" [string|list(string)=''] Name of the filter to mark components as 'Do Not Change'.
Use '_kibom_dnc' for the default KiBoM behavior """
def filter(self, comps, reset):
def filter(self, comps):
# Apply all the filters
apply_in_bom_filter(comps, self.exclude_filter, reset)
apply_fitted_filter(comps, self.dnf_filter, reset)
apply_fixed_filter(comps, self.dnc_filter, reset)
apply_exclude_filter(comps, self.exclude_filter)
apply_fitted_filter(comps, self.dnf_filter)
apply_fixed_filter(comps, self.dnc_filter)

View File

@ -8,6 +8,7 @@ Implements the IBoM variants mechanism.
"""
from .optionable import Optionable
from .gs import GS
from .misc import IFILL_MECHANICAL
from .fil_base import BaseFilter
from .macros import macros, document, variant_class # noqa: F401
from . import log
@ -47,7 +48,7 @@ class IBoM(BaseVariant): # noqa: F821
def config(self):
super().config()
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter')
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter', IFILL_MECHANICAL)
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter')
self.variants_blacklist = self._force_list(self.variants_blacklist)
@ -65,15 +66,16 @@ class IBoM(BaseVariant): # noqa: F821
return True
return False
def filter(self, comps, reset=False):
super().filter(comps, reset)
def filter(self, comps):
super().filter(comps)
logger.debug("Applying IBoM style variants `{}`".format(self.name))
# Make black/white lists case insensitive
self.variants_whitelist = [v.lower() for v in self.variants_whitelist]
self.variants_blacklist = [v.lower() for v in self.variants_blacklist]
# Apply to all the components
for c in comps:
if not (c.fitted and c.in_bom):
logger.debug("{} {} {}".format(c.ref, c.fitted, c.included))
if not (c.fitted and c.included):
# Don't check if we already discarded it
continue
c.fitted = not self.skip_component(c)

View File

@ -91,11 +91,11 @@ class KiBoM(BaseVariant): # noqa: F821
# No match
return not exclusive
def filter(self, comps, reset=False):
super().filter(comps, reset)
def filter(self, comps):
super().filter(comps)
logger.debug("Applying KiBoM style variants `{}`".format(self.name))
for c in comps:
if not (c.fitted and c.in_bom):
if not (c.fitted and c.included):
# Don't check if we already discarded it
continue
value = c.value.lower()

View File

@ -5,7 +5,7 @@
(drawings 5)
(tracks 0)
(zones 0)
(modules 5)
(modules 6)
(nets 9)
)
@ -116,6 +116,27 @@
(add_net "Net-(R2-Pad2)")
)
(module Fiducial:Fiducial_0.5mm_Mask1mm (layer F.Cu) (tedit 5C18CB26) (tstamp 5F57EE4F)
(at 139.6746 82.9564)
(descr "Circular Fiducial, 0.5mm bare copper, 1mm soldermask opening (Level C)")
(tags fiducial)
(path /5F57EDDB)
(attr virtual)
(fp_text reference FID1 (at 0 -1.5) (layer F.SilkS)
(effects (font (size 1 1) (thickness 0.15)))
)
(fp_text value Fiducial (at 0 1.5) (layer F.Fab)
(effects (font (size 1 1) (thickness 0.15)))
)
(fp_circle (center 0 0) (end 0.5 0) (layer F.Fab) (width 0.1))
(fp_circle (center 0 0) (end 0.75 0) (layer F.CrtYd) (width 0.05))
(fp_text user %R (at 0 0) (layer F.Fab)
(effects (font (size 0.2 0.2) (thickness 0.04)))
)
(pad "" smd circle (at 0 0) (size 0.5 0.5) (layers F.Cu F.Mask)
(solder_mask_margin 0.25) (clearance 0.25))
)
(module Resistor_SMD:R_0805_2012Metric (layer F.Cu) (tedit 5B36C52B) (tstamp 5F503C4A)
(at 148.555 89.154)
(descr "Resistor SMD 0805 (2012 Metric), square (rectangular) end terminal, IPC_7351 nominal, (Body size source: https://docs.google.com/spreadsheets/d/1BsfQQcO9C6DZCsRaXUlFlo91Tg2WpOkGARC1WS5S8t0/edit?usp=sharing), generated with kicad-footprint-generator")
@ -142,8 +163,10 @@
(fp_text user %R (at 0 0) (layer F.Fab)
(effects (font (size 0.5 0.5) (thickness 0.08)))
)
(pad 1 smd roundrect (at -0.9375 0) (size 0.975 1.4) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.25))
(pad 2 smd roundrect (at 0.9375 0) (size 0.975 1.4) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.25))
(pad 1 smd roundrect (at -0.9375 0) (size 0.975 1.4) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.25)
(net 6 "Net-(R1-Pad1)"))
(pad 2 smd roundrect (at 0.9375 0) (size 0.975 1.4) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.25)
(net 5 "Net-(R1-Pad2)"))
(model ${KISYS3DMOD}/Resistor_SMD.3dshapes/R_0805_2012Metric.wrl
(at (xyz 0 0 0))
(scale (xyz 1 1 1))
@ -265,7 +288,7 @@
(descr "Capacitor SMD 0805 (2012 Metric), square (rectangular) end terminal, IPC_7351 nominal, (Body size source: https://docs.google.com/spreadsheets/d/1BsfQQcO9C6DZCsRaXUlFlo91Tg2WpOkGARC1WS5S8t0/edit?usp=sharing), generated with kicad-footprint-generator")
(tags capacitor)
(path /5F43BEC2)
(attr virtual)
(attr smd)
(fp_text reference C1 (at 0 -1.65) (layer F.SilkS)
(effects (font (size 1 1) (thickness 0.15)))
)
@ -305,9 +328,9 @@
(gr_text "Bogus component.\nNot in schematic." (at 161.163 89.281) (layer Cmts.User)
(effects (font (size 1.5 1.5) (thickness 0.3)))
)
(gr_line (start 133.35 83.82) (end 133.35 93.98) (layer Edge.Cuts) (width 0.1) (tstamp 5F496ACC))
(gr_line (start 146.05 83.82) (end 133.35 83.82) (layer Edge.Cuts) (width 0.1))
(gr_line (start 146.05 93.98) (end 146.05 83.82) (layer Edge.Cuts) (width 0.1))
(gr_line (start 133.35 80.4) (end 133.35 93.98) (layer Edge.Cuts) (width 0.1) (tstamp 5F496ACC))
(gr_line (start 146.05 80.4) (end 133.35 80.4) (layer Edge.Cuts) (width 0.1) (tstamp 5F57EEAF))
(gr_line (start 146.05 93.98) (end 146.05 80.4) (layer Edge.Cuts) (width 0.1))
(gr_line (start 133.35 93.98) (end 146.05 93.98) (layer Edge.Cuts) (width 0.1))
)

View File

@ -65,4 +65,15 @@ F 4 "T1" H 2500 1700 50 0001 C CNN "Config"
$EndComp
Text Notes 5950 3200 0 118 ~ 0
The test tests the following \nvariants matrix:\n production test default\nC1 X\nC2 X X\nR1 X X X\nR2 X X\n\nproduction: blacklist T2\ntest: blacklist T1\ndefault: whitelist T1,default \n blacklist T2,T3
$Comp
L Mechanical:Fiducial FID1
U 1 1 5F57EDDB
P 1750 2250
F 0 "FID1" H 1835 2296 50 0000 L CNN
F 1 "Fiducial" H 1835 2205 50 0000 L CNN
F 2 "Fiducial:Fiducial_0.5mm_Mask1mm" H 1750 2250 50 0001 C CNN
F 3 "~" H 1750 2250 50 0001 C CNN
1 1750 2250
1 0 0 -1
$EndComp
$EndSCHEMATC

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -104,7 +104,6 @@ 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, '')
@ -114,5 +113,5 @@ def test_gerber_variant_1():
check_components(ctx, 'production', prj, ['F_Paste', 'F_Adhes'], '_(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', 'F_Adhes'], '_(test)', ['C1', 'R2'], ['R1', 'R3', 'C2'])
check_components(ctx, 'test', prj, ['F_Paste', 'F_Adhes'], '_(test)', ['R2'], ['C1', 'R1', 'R3', 'C2'])
ctx.clean_up()

View File

@ -1222,9 +1222,9 @@ def test_int_bom_variant_t2s():
ctx.clean_up()
def test_int_bom_variant_t2i():
def test_int_bom_variant_t2if():
prj = 'kibom-variant_3'
ctx = context.TestContextSCH('test_int_bom_variant_t2i', prj, 'int_bom_var_t2i_csv', BOM_DIR)
ctx = context.TestContextSCH('test_int_bom_variant_t2if', prj, 'int_bom_var_t2i_csv', BOM_DIR)
ctx.run()
rows, header, info = ctx.load_csv(prj+'-bom.csv')
ref_column = header.index(REF_COLUMN_NAME)
@ -1291,11 +1291,11 @@ def test_int_bom_fil_2():
ctx.run()
rows, header, info = ctx.load_csv('smd.csv')
ref_column = header.index(REF_COLUMN_NAME)
check_kibom_test_netlist(rows, ref_column, 2, None, ['R2', 'C1-C2'])
check_kibom_test_netlist(rows, ref_column, 3, None, ['R2', 'C2', 'FID1'])
rows, header, info = ctx.load_csv('tht.csv')
check_kibom_test_netlist(rows, ref_column, 2, None, ['R1', 'C1'])
check_kibom_test_netlist(rows, ref_column, 3, None, ['R1', 'C1', 'FID1'])
rows, header, info = ctx.load_csv('virtual.csv')
check_kibom_test_netlist(rows, ref_column, 2, None, ['R1-R2', 'C2'])
check_kibom_test_netlist(rows, ref_column, 2, None, ['R1-R2', 'C1-C2'])
ctx.search_err(r".?R3.? component in board, but not in schematic")
ctx.clean_up()

View File

@ -108,6 +108,7 @@ def test_pcbdraw_variant_1():
# We use 30% because different versions of the tools are generating large differences
# in the borders. With 30% these differences are removed and we still detect is a
# components was removed.
# Expected: R1 and R2 populated
ctx.compare_image(fname, fuzz='30%')
ctx.clean_up()
@ -119,6 +120,7 @@ def test_pcbdraw_variant_2():
# Check all outputs are there
fname = prj+'-top-C1.png'
ctx.expect_out_file(fname)
# Expected: R1 and R2 populated + C1 manually added
ctx.compare_image(fname, fuzz='30%')
ctx.clean_up()

View File

@ -167,5 +167,5 @@ def test_position_variant_t2i():
rows, header, info = ctx.load_csv(prj+'-both_pos_(production).csv')
check_comps(rows, ['C2', 'R1', 'R2', 'R3'])
rows, header, info = ctx.load_csv(prj+'-both_pos_(test).csv')
check_comps(rows, ['C2', 'R1', 'R3'])
check_comps(rows, ['C1', 'C2', 'R1', 'R3'])
ctx.clean_up()

View File

@ -337,7 +337,7 @@ class TestContext(object):
image,
reference,
# Avoid the part where KiCad version is printed
'-crop', '100%x88%+0+0', '+repage',
'-crop', '100%x87%+0+0', '+repage',
'-colorspace', 'RGB',
self.get_out_path(diff)]
logging.debug('Comparing images with: '+str(cmd))