Custom reports for plot outputs (i.e. custom gerber job generation)

This commit is contained in:
Salvador E. Tropea 2021-01-14 10:24:04 -03:00
parent aa3025b348
commit 9f4763c36b
4 changed files with 121 additions and 2 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Now layers can be selected using the default KiCad names. - Now layers can be selected using the default KiCad names.
- More control over the name of the drill and gerber files. - More control over the name of the drill and gerber files.
- More options to customize the excellon output. - More options to customize the excellon output.
- Custom reports for plot outputs (i.e. custom gerber job generation)
### Changed ### Changed
- Now the default output name applies to the DRC and ERC report names. - Now the default output name applies to the DRC and ERC report names.

View File

@ -662,6 +662,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 `dxf` output. - `options`: [dict] Options for the `dxf` output.
* Valid keys: * Valid keys:
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
- `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale).
@ -759,6 +764,11 @@ Next time you need this list just use an alias, like this:
* Valid keys: * Valid keys:
- `create_gerber_job_file`: [boolean=true] Creates a file with information about all the generated gerbers. - `create_gerber_job_file`: [boolean=true] Creates a file with information about all the generated gerbers.
You can use it in gerbview to load all gerbers at once. You can use it in gerbview to load all gerbers at once.
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `disable_aperture_macros`: [boolean=false] Disable aperture macros (workaround for buggy CAM software) (KiCad 6). - `disable_aperture_macros`: [boolean=false] Disable aperture macros (workaround for buggy CAM software) (KiCad 6).
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
@ -801,6 +811,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 `hpgl` output. - `options`: [dict] Options for the `hpgl` output.
* Valid keys: * Valid keys:
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
- `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale).
@ -1025,6 +1040,11 @@ Next time you need this list just use an alias, like this:
This output is what you get from the File/Plot menu in pcbnew. This output is what you get from the File/Plot menu in pcbnew.
* Valid keys: * Valid keys:
- `comment`: [string=''] A comment for documentation purposes. - `comment`: [string=''] A comment for documentation purposes.
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `dir`: [string='.'] Output directory for the generated files. - `dir`: [string='.'] Output directory for the generated files.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
@ -1045,6 +1065,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` output. - `options`: [dict] Options for the `pdf` output.
* Valid keys: * Valid keys:
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
- `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale).
@ -1160,6 +1185,11 @@ Next time you need this list just use an alias, like this:
- `options`: [dict] Options for the `ps` output. - `options`: [dict] Options for the `ps` output.
* Valid keys: * Valid keys:
- `a4_output`: [boolean=true] force A4 paper size. - `a4_output`: [boolean=true] force A4 paper size.
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
- `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale).
@ -1243,6 +1273,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 `svg` output. - `options`: [dict] Options for the `svg` output.
* Valid keys: * Valid keys:
- `custom_reports`: [list(dict)] A list of customized reports for the manufacturer.
* Valid keys:
- `content`: [string=''] Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER.
- `output`: [string='Custom_report.txt'] File name for the custom report.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. - `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. A short-cut to use for simple cases where a variant is an overkill.
- `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale).

View File

@ -168,6 +168,13 @@ outputs:
type: 'dxf' type: 'dxf'
dir: 'Example/dxf_dir' dir: 'Example/dxf_dir'
options: options:
# [list(dict)] A list of customized reports for the manufacturer
custom_reports:
# [string=''] Content for the report. Use ${basename} for the project name without extension.
# Use ${filename(LAYER)} for the file corresponding to LAYER
- content: ''
# [string='Custom_report.txt'] File name for the custom report
output: 'Custom_report.txt'
# [string|list(string)=''] Name of the filter to mark components as not fitted. # [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 # A short-cut to use for simple cases where a variant is an overkill
dnf_filter: '' dnf_filter: ''
@ -291,6 +298,13 @@ outputs:
# [boolean=true] Creates a file with information about all the generated gerbers. # [boolean=true] Creates a file with information about all the generated gerbers.
# You can use it in gerbview to load all gerbers at once # You can use it in gerbview to load all gerbers at once
create_gerber_job_file: true create_gerber_job_file: true
# [list(dict)] A list of customized reports for the manufacturer
custom_reports:
# [string=''] Content for the report. Use ${basename} for the project name without extension.
# Use ${filename(LAYER)} for the file corresponding to LAYER
- content: ''
# [string='Custom_report.txt'] File name for the custom report
output: 'Custom_report.txt'
# [boolean=false] Disable aperture macros (workaround for buggy CAM software) (KiCad 6) # [boolean=false] Disable aperture macros (workaround for buggy CAM software) (KiCad 6)
disable_aperture_macros: false disable_aperture_macros: false
# [string|list(string)=''] Name of the filter to mark components as not fitted. # [string|list(string)=''] Name of the filter to mark components as not fitted.
@ -347,6 +361,13 @@ outputs:
type: 'hpgl' type: 'hpgl'
dir: 'Example/hpgl_dir' dir: 'Example/hpgl_dir'
options: options:
# [list(dict)] A list of customized reports for the manufacturer
custom_reports:
# [string=''] Content for the report. Use ${basename} for the project name without extension.
# Use ${filename(LAYER)} for the file corresponding to LAYER
- content: ''
# [string='Custom_report.txt'] File name for the custom report
output: 'Custom_report.txt'
# [string|list(string)=''] Name of the filter to mark components as not fitted. # [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 # A short-cut to use for simple cases where a variant is an overkill
dnf_filter: '' dnf_filter: ''
@ -663,6 +684,13 @@ outputs:
type: 'pdf' type: 'pdf'
dir: 'Example/pdf_dir' dir: 'Example/pdf_dir'
options: options:
# [list(dict)] A list of customized reports for the manufacturer
custom_reports:
# [string=''] Content for the report. Use ${basename} for the project name without extension.
# Use ${filename(LAYER)} for the file corresponding to LAYER
- content: ''
# [string='Custom_report.txt'] File name for the custom report
output: 'Custom_report.txt'
# [string|list(string)=''] Name of the filter to mark components as not fitted. # [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 # A short-cut to use for simple cases where a variant is an overkill
dnf_filter: '' dnf_filter: ''
@ -789,6 +817,13 @@ outputs:
options: options:
# [boolean=true] force A4 paper size # [boolean=true] force A4 paper size
a4_output: true a4_output: true
# [list(dict)] A list of customized reports for the manufacturer
custom_reports:
# [string=''] Content for the report. Use ${basename} for the project name without extension.
# Use ${filename(LAYER)} for the file corresponding to LAYER
- content: ''
# [string='Custom_report.txt'] File name for the custom report
output: 'Custom_report.txt'
# [string|list(string)=''] Name of the filter to mark components as not fitted. # [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 # A short-cut to use for simple cases where a variant is an overkill
dnf_filter: '' dnf_filter: ''
@ -891,6 +926,13 @@ outputs:
type: 'svg' type: 'svg'
dir: 'Example/svg_dir' dir: 'Example/svg_dir'
options: options:
# [list(dict)] A list of customized reports for the manufacturer
custom_reports:
# [string=''] Content for the report. Use ${basename} for the project name without extension.
# Use ${filename(LAYER)} for the file corresponding to LAYER
- content: ''
# [string='Custom_report.txt'] File name for the custom report
output: 'Custom_report.txt'
# [string|list(string)=''] Name of the filter to mark components as not fitted. # [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 # A short-cut to use for simple cases where a variant is an overkill
dnf_filter: '' dnf_filter: ''

View File

@ -6,9 +6,11 @@
# Project: KiBot (formerly KiPlot) # Project: KiBot (formerly KiPlot)
# Adapted from: https://github.com/johnbeard/kiplot # Adapted from: https://github.com/johnbeard/kiplot
import os import os
import re
from pcbnew import GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer, F_Cu, B_Cu, Edge_Cuts from pcbnew import GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer, F_Cu, B_Cu, Edge_Cuts
from .out_base import (BaseOutput) from .optionable import Optionable
from .error import (PlotError, KiPlotConfigurationError) from .out_base import BaseOutput
from .error import PlotError, KiPlotConfigurationError
from .layer import Layer from .layer import Layer
from .gs import GS from .gs import GS
from .misc import KICAD_VERSION_5_99, W_NOLAYER from .misc import KICAD_VERSION_5_99, W_NOLAYER
@ -19,6 +21,17 @@ from . import log
logger = log.get_logger(__name__) logger = log.get_logger(__name__)
class CustomReport(Optionable):
def __init__(self):
super().__init__()
with document:
self.output = 'Custom_report.txt'
""" File name for the custom report """
self.content = ''
""" Content for the report. Use ${basename} for the project name without extension.
Use ${filename(LAYER)} for the file corresponding to LAYER """
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):
@ -47,8 +60,15 @@ class AnyLayerOptions(VariantOptions):
Example '.g%n' """ Example '.g%n' """
self.edge_cut_extension = '' self.edge_cut_extension = ''
""" Used to configure the edge cuts layer extension for Protel mode """ """ Used to configure the edge cuts layer extension for Protel mode """
self.custom_reports = CustomReport
""" [list(dict)] A list of customized reports for the manufacturer """
super().__init__() super().__init__()
def config(self):
super().config()
if isinstance(self.custom_reports, type):
self.custom_reports = []
def _configure_plot_ctrl(self, po, output_dir): def _configure_plot_ctrl(self, po, output_dir):
logger.debug("Configuring plot controller for output") logger.debug("Configuring plot controller for output")
po.SetOutputDirectory(output_dir) po.SetOutputDirectory(output_dir)
@ -93,6 +113,7 @@ class AnyLayerOptions(VariantOptions):
# Apply the variants and filters # Apply the variants and filters
exclude = self.filter_components(board) exclude = self.filter_components(board)
# Plot every layer in the output # Plot every layer in the output
generated = {}
layers = Layer.solve(layers) layers = Layer.solve(layers)
for la in layers: for la in layers:
suffix = la.suffix suffix = la.suffix
@ -134,9 +155,29 @@ class AnyLayerOptions(VariantOptions):
os.rename(k_filename, filename) os.rename(k_filename, filename)
if create_job: if create_job:
jobfile_writer.AddGbrFile(id, os.path.basename(filename)) jobfile_writer.AddGbrFile(id, os.path.basename(filename))
generated[la.layer] = os.path.basename(filename)
# Create the job file # Create the job file
if create_job: if create_job:
jobfile_writer.CreateJobFile(self.expand_filename(output_dir, po.gerber_job_file, 'job', 'gbrjob')) jobfile_writer.CreateJobFile(self.expand_filename(output_dir, po.gerber_job_file, 'job', 'gbrjob'))
# Custom reports
regex_fname = re.compile(r'\$\{filename\(.*\)\}')
for report in self.custom_reports:
filename = report.output
content = report.content
# Replace special white spaces
content = content.replace('\\r', chr(13))
content = content.replace('\\n', chr(10))
content = content.replace('\\t', chr(9))
# Replace file names, compatible with gerber_zipper_action
content = content.replace('${basename}', GS.pcb_basename)
for name, file in generated.items():
content = content.replace('${filename('+name+')}', file)
# Replace unused layers
content = regex_fname.sub('', content)
# Create the report
logger.debug('Creating custom report `'+filename+'`')
with open(os.path.join(output_dir, filename), 'wt') as f:
f.write(content)
# Restore the eliminated layers # Restore the eliminated layers
if exclude: if exclude:
self.unfilter_components(board) self.unfilter_components(board)