Added variants support to the PCB print (PDF)

Needs some adjustement, but is working.
This commit is contained in:
Salvador E. Tropea 2020-09-07 19:26:16 -03:00
parent 0e394b468b
commit 23e46df1c5
11 changed files with 146 additions and 30 deletions

View File

@ -18,8 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Marking components as "Do Not Fit" - Marking components as "Do Not Fit"
- Marking components as "Do Not Change" - Marking components as "Do Not Change"
- The internal BoM format supports KiBoM and IBoM style variants - The internal BoM format supports KiBoM and IBoM style variants
- Schematic print to PDF/SVG support for variants. Not fitted components are - Schematic/PCB print to PDF/SVG support for variants. Not fitted components
crossed. are crossed.
- Position (Pick & Place) support for variants. - Position (Pick & Place) support for variants.
- All plot formats (gerber, pdf, svg, etc.) support for variants: - All plot formats (gerber, pdf, svg, etc.) support for variants:
- Pads removed from *.Paste - Pads removed from *.Paste

View File

@ -115,6 +115,7 @@ gen_ref:
src/kibot -b tests/board_samples/kibom-variant_4.kicad_pcb -c tests/yaml_samples/pdf_variant_1.kibot.yaml -d $(REFDIR) src/kibot -b tests/board_samples/kibom-variant_4.kicad_pcb -c tests/yaml_samples/pdf_variant_1.kibot.yaml -d $(REFDIR)
src/kibot -b tests/board_samples/kibom-variant_3.kicad_pcb -c tests/yaml_samples/pcbdraw_variant_1.kibot.yaml -d $(REFDIR) src/kibot -b tests/board_samples/kibom-variant_3.kicad_pcb -c tests/yaml_samples/pcbdraw_variant_1.kibot.yaml -d $(REFDIR)
src/kibot -b tests/board_samples/kibom-variant_3.kicad_pcb -c tests/yaml_samples/pcbdraw_variant_2.kibot.yaml -d $(REFDIR) src/kibot -b tests/board_samples/kibom-variant_3.kicad_pcb -c tests/yaml_samples/pcbdraw_variant_2.kibot.yaml -d $(REFDIR)
src/kibot -b tests/board_samples/kibom-variant_3.kicad_pcb -c tests/yaml_samples/print_pcb_variant_1.kibot.yaml -d $(REFDIR)
cp -a $(REFILL).ok $(REFILL) cp -a $(REFILL).ok $(REFILL)
doc: doc:

View File

@ -831,8 +831,11 @@ Next time you need this list just use an alias, like this:
- `name`: [string=''] Used to identify this particular output definition. - `name`: [string=''] Used to identify this particular output definition.
- `options`: [dict] Options for the `pdf_pcb_print` output. - `options`: [dict] Options for the `pdf_pcb_print` output.
* Valid keys: * Valid keys:
- `dnf_filter`: [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.
- `output`: [string='%f-%i%v.%x'] filename for the output PDF (%i=layers, %x=pdf). Affected by global options. - `output`: [string='%f-%i%v.%x'] filename for the output PDF (%i=layers, %x=pdf). Affected by global options.
- *output_name*: Alias for output. - *output_name*: Alias for output.
- `variant`: [string=''] Board variant to apply.
* PDF Schematic Print (Portable Document Format) * PDF Schematic Print (Portable Document Format)
* Type: `pdf_sch_print` * Type: `pdf_sch_print`

View File

@ -648,9 +648,14 @@ outputs:
type: 'pdf_pcb_print' type: 'pdf_pcb_print'
dir: 'Example/pdf_pcb_print_dir' dir: 'Example/pdf_pcb_print_dir'
options: options:
# [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
dnf_filter: ''
# [string='%f-%i%v.%x'] filename for the output PDF (%i=layers, %x=pdf). Affected by global options # [string='%f-%i%v.%x'] filename for the output PDF (%i=layers, %x=pdf). Affected by global options
output: '%f-%i%v.%x' output: '%f-%i%v.%x'
# `output_name` is an alias for `output` # `output_name` is an alias for `output`
# [string=''] Board variant to apply
variant: ''
layers: all layers: all
# PDF Schematic Print (Portable Document Format): # PDF Schematic Print (Portable Document Format):

View File

@ -77,3 +77,25 @@ DNC = {
"no change": 1, "no change": 1,
"fixed": 1 "fixed": 1
} }
class Rect(object):
""" What KiCad returns isn't a real wxWidget's wxRect.
Here I add what I really need """
def __init__(self):
self.x1 = None
self.y1 = None
self.x2 = None
self.y2 = None
def Union(self, wxRect):
if self.x1 is None:
self.x1 = wxRect.x
self.y1 = wxRect.y
self.x2 = wxRect.x+wxRect.width
self.y2 = wxRect.y+wxRect.height
else:
self.x1 = min(self.x1, wxRect.x)
self.y1 = min(self.y1, wxRect.y)
self.x2 = max(self.x2, wxRect.x+wxRect.width)
self.y2 = max(self.y2, wxRect.y+wxRect.height)

View File

@ -11,7 +11,7 @@ from .out_base import (BaseOutput)
from .error import (PlotError, KiPlotConfigurationError) from .error import (PlotError, KiPlotConfigurationError)
from .layer import Layer from .layer import Layer
from .gs import GS from .gs import GS
from .misc import UI_VIRTUAL from .misc import UI_VIRTUAL, Rect
from .out_base import VariantOptions from .out_base import VariantOptions
from .macros import macros, document # noqa: F401 from .macros import macros, document # noqa: F401
from . import log from . import log
@ -19,28 +19,6 @@ from . import log
logger = log.get_logger(__name__) logger = log.get_logger(__name__)
class Rect(object):
""" What KiCad returns isn't a real wxWidget's wxRect.
Here I add what I really need """
def __init__(self):
self.x1 = None
self.y1 = None
self.x2 = None
self.y2 = None
def Union(self, wxRect):
if self.x1 is None:
self.x1 = wxRect.x
self.y1 = wxRect.y
self.x2 = wxRect.x+wxRect.width
self.y2 = wxRect.y+wxRect.height
else:
self.x1 = min(self.x1, wxRect.x)
self.y1 = min(self.y1, wxRect.y)
self.x2 = max(self.x2, wxRect.x+wxRect.width)
self.y2 = max(self.y2, wxRect.y+wxRect.height)
class AnyLayerOptions(VariantOptions): class AnyLayerOptions(VariantOptions):
""" Base class for: DXF, Gerber, HPGL, PDF, PS and SVG """ """ Base class for: DXF, Gerber, HPGL, PDF, PS and SVG """
def __init__(self): def __init__(self):

View File

@ -3,12 +3,15 @@
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial # Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0 # License: GPL-3.0
# Project: KiBot (formerly KiPlot) # Project: KiBot (formerly KiPlot)
import os
from tempfile import NamedTemporaryFile
from pcbnew import EDGE_MODULE, wxPoint
from .pre_base import BasePreFlight from .pre_base import BasePreFlight
from .error import (KiPlotConfigurationError) from .error import (KiPlotConfigurationError)
from .gs import (GS) from .gs import (GS)
from .kiplot import check_script, exec_with_retry from .kiplot import check_script, exec_with_retry
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT) from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT, Rect, UI_VIRTUAL)
from .optionable import BaseOptions from .out_base import VariantOptions
from .macros import macros, document, output_class # noqa: F401 from .macros import macros, document, output_class # noqa: F401
from .layer import Layer from .layer import Layer
from . import log from . import log
@ -16,7 +19,7 @@ from . import log
logger = log.get_logger(__name__) logger = log.get_logger(__name__)
class PDF_Pcb_PrintOptions(BaseOptions): class PDF_Pcb_PrintOptions(VariantOptions):
def __init__(self): def __init__(self):
with document: with document:
self.output = GS.def_global_output self.output = GS.def_global_output
@ -25,7 +28,79 @@ class PDF_Pcb_PrintOptions(BaseOptions):
""" {output} """ """ {output} """
super().__init__() 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)
# 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)
return fname
def run(self, output_dir, board, layers): def run(self, output_dir, board, layers):
super().run(board, layers)
check_script(CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, '1.4.1') check_script(CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, '1.4.1')
layers = Layer.solve(layers) layers = Layer.solve(layers)
# Output file name # Output file name
@ -34,7 +109,8 @@ class PDF_Pcb_PrintOptions(BaseOptions):
cmd = [CMD_PCBNEW_PRINT_LAYERS, 'export', '--output_name', output] cmd = [CMD_PCBNEW_PRINT_LAYERS, 'export', '--output_name', output]
if BasePreFlight.get_option('check_zone_fills'): if BasePreFlight.get_option('check_zone_fills'):
cmd.append('-f') cmd.append('-f')
cmd.extend([GS.pcb_file, output_dir]) board_name = self.filter_components(board)
cmd.extend([board_name, output_dir])
if GS.debug_enabled: if GS.debug_enabled:
cmd.insert(1, '-vv') cmd.insert(1, '-vv')
cmd.insert(1, '-r') cmd.insert(1, '-r')
@ -42,6 +118,9 @@ class PDF_Pcb_PrintOptions(BaseOptions):
cmd.extend([la.layer for la in layers]) cmd.extend([la.layer for la in layers])
# Execute it # Execute it
ret = exec_with_retry(cmd) ret = exec_with_retry(cmd)
# Remove the temporal PCB
if board_name != GS.pcb_file:
os.remove(board_name)
if ret: # pragma: no cover if ret: # pragma: no cover
# We check all the arguments, we even load the PCB # We check all the arguments, we even load the PCB
# A fail here isn't easy to reproduce # A fail here isn't easy to reproduce

Binary file not shown.

View File

@ -39,3 +39,14 @@ def test_print_pcb_refill():
ctx.expect_out_file(PDF_FILE_B) ctx.expect_out_file(PDF_FILE_B)
ctx.compare_image(PDF_FILE_B) ctx.compare_image(PDF_FILE_B)
def test_print_variant_1():
prj = 'kibom-variant_3'
ctx = context.TestContext('test_print_variant_1', prj, 'print_pcb_variant_1', '')
ctx.run()
# Check all outputs are there
fname = prj+'-F_Fab.pdf'
ctx.expect_out_file(fname)
ctx.compare_pdf(fname)
ctx.clean_up()

View File

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

View File

@ -0,0 +1,17 @@
# Example KiBot config file
kibot:
version: 1
variants:
- name: 'default'
comment: 'Default variant'
type: ibom
variants_blacklist: T2,T3
outputs:
- name: 'pdf_default'
comment: "PCB print w/variant"
type: pdf_pcb_print
options:
variant: default
layers: F.Fab