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 Change"
- The internal BoM format supports KiBoM and IBoM style variants
- Schematic print to PDF/SVG support for variants. Not fitted components are
crossed.
- Schematic/PCB print to PDF/SVG support for variants. Not fitted components
are crossed.
- Position (Pick & Place) support for variants.
- All plot formats (gerber, pdf, svg, etc.) support for variants:
- 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_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/print_pcb_variant_1.kibot.yaml -d $(REFDIR)
cp -a $(REFILL).ok $(REFILL)
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.
- `options`: [dict] Options for the `pdf_pcb_print` output.
* 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_name*: Alias for output.
- `variant`: [string=''] Board variant to apply.
* PDF Schematic Print (Portable Document Format)
* Type: `pdf_sch_print`

View File

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

View File

@ -77,3 +77,25 @@ DNC = {
"no change": 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 .layer import Layer
from .gs import GS
from .misc import UI_VIRTUAL
from .misc import UI_VIRTUAL, Rect
from .out_base import VariantOptions
from .macros import macros, document # noqa: F401
from . import log
@ -19,28 +19,6 @@ from . import log
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):
""" Base class for: DXF, Gerber, HPGL, PDF, PS and SVG """
def __init__(self):

View File

@ -3,12 +3,15 @@
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# 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)
from .optionable import BaseOptions
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT, Rect, UI_VIRTUAL)
from .out_base import VariantOptions
from .macros import macros, document, output_class # noqa: F401
from .layer import Layer
from . import log
@ -16,7 +19,7 @@ from . import log
logger = log.get_logger(__name__)
class PDF_Pcb_PrintOptions(BaseOptions):
class PDF_Pcb_PrintOptions(VariantOptions):
def __init__(self):
with document:
self.output = GS.def_global_output
@ -25,7 +28,79 @@ class PDF_Pcb_PrintOptions(BaseOptions):
""" {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)
# 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):
super().run(board, layers)
check_script(CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, '1.4.1')
layers = Layer.solve(layers)
# Output file name
@ -34,7 +109,8 @@ class PDF_Pcb_PrintOptions(BaseOptions):
cmd = [CMD_PCBNEW_PRINT_LAYERS, 'export', '--output_name', output]
if BasePreFlight.get_option('check_zone_fills'):
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:
cmd.insert(1, '-vv')
cmd.insert(1, '-r')
@ -42,6 +118,9 @@ class PDF_Pcb_PrintOptions(BaseOptions):
cmd.extend([la.layer for la in layers])
# Execute it
ret = exec_with_retry(cmd)
# Remove the temporal PCB
if board_name != GS.pcb_file:
os.remove(board_name)
if ret: # pragma: no cover
# We check all the arguments, we even load the PCB
# 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.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,
reference,
# Avoid the part where KiCad version is printed
'-crop', '100%x92%+0+0', '+repage',
'-crop', '100%x88%+0+0', '+repage',
'-colorspace', 'RGB',
self.get_out_path(diff)]
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