Custom reports for plot outputs (i.e. custom gerber job generation)
This commit is contained in:
parent
aa3025b348
commit
9f4763c36b
|
|
@ -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.
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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: ''
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue