Added variants support to the PCB print (PDF)
Needs some adjustement, but is working.
This commit is contained in:
parent
0e394b468b
commit
23e46df1c5
|
|
@ -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
|
||||||
|
|
|
||||||
1
Makefile
1
Makefile
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue