Added the first stepof the new `--quick-start` option

- Should be a way to quickly start using KiBot without any config
This commit is contained in:
Salvador E. Tropea 2022-04-26 09:51:14 -03:00
parent 43c5f4f12e
commit 56030c5dc9
34 changed files with 1270 additions and 126 deletions

View File

@ -3,3 +3,6 @@ include LICENSE
include README.md
include kibot/report_templates/*.txt
include kibot/kicad_colors/*.json
include kibot/kicad_layouts/*.kicad_wks
include kibot/config_templates/gerbers/*.yaml
include kibot/config_templates/bom/*.yaml

View File

@ -1705,6 +1705,7 @@ Next time you need this list just use an alias, like this:
* Valid keys:
- `add_background`: [boolean=false] Add a background to the pages, see `background_color`.
- `background_color`: [string='#FFFFFF'] Color for the background when `add_background` is enabled.
- `background_image`: [string=''] Background image, must be an SVG, only when `add_background` is enabled.
- `blind_via_color`: [string=''] Color used for blind/buried `colored_vias`.
- `color_theme`: [string='_builtin_classic'] Selects the color theme. Only applies to KiCad 6.
To use the KiCad 6 default colors select `_builtin_default`.
@ -2195,7 +2196,8 @@ Next time you need this list just use an alias, like this:
In Debian/Ubuntu environments: install `pandoc`, `texlive-latex-base` and `texlive-latex-recommended`.
- `eurocircuits_class_target`: [string='10F'] Which Eurocircuits class are we aiming at.
- `output`: [string='%f-%i%I%v.%x'] Output file name (%i='report', %x='txt'). Affected by global options.
- `template`: [string='full'] Name for one of the internal templates (full, simple) or a custom template file.
- `template`: [string='full'] Name for one of the internal templates (full, full_svg, simple) or a custom template file.
Note: when converting to PDF PanDoc can fail on some Unicode values (use `simple_ASCII`).
- `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
@ -2812,6 +2814,7 @@ Usage:
[-q | -v...] [-i] [-C] [-m MKFILE] [-g DEF]... [TARGET...]
kibot [-v...] [-b BOARD] [-e SCHEMA] [-c PLOT_CONFIG] --list
kibot [-v...] [-b BOARD] [-d OUT_DIR] [-p | -P] --example
kibot [-v...] --quick-start
kibot [-v...] --help-filters
kibot [-v...] --help-global-options
kibot [-v...] --help-list-outputs

View File

@ -1,6 +1,14 @@
#!/usr/bin/make
all: ../README.md samples/generic_plot.kibot.yaml
all: ../README.md samples/generic_plot.kibot.yaml ../kibot/report_templates/report_full_svg.txt ../kibot/config_templates/bom/MacroFab_XYRS.kibot.yaml \
../kibot/config_templates/gerber/Elecrow.kibot.yaml ../kibot/config_templates/gerber/FusionPCB.kibot.yaml \
../kibot/config_templates/gerber/JLCPCB.kibot.yaml ../kibot/config_templates/gerber/PCBWay.kibot.yaml
../kibot/config_templates/gerber/%.kibot.yaml: samples/%.kibot.yaml
cp $< $@
../kibot/config_templates/bom/%.kibot.yaml: samples/%.kibot.yaml
cp $< $@
../README.md: README.in replace_tags.pl ../kibot/out_*.py ../kibot/pre_*.py ../kibot/fil_*.py ../kibot/__main__.py ../kibot/config_reader.py
cat README.in | perl replace_tags.pl > ../README.md
@ -9,3 +17,6 @@ samples/generic_plot.kibot.yaml: ../kibot/out_*.py ../kibot/pre_*.py ../kibot/co
rm -f example_template.kibot.yaml
../src/kibot -v --example
mv example_template.kibot.yaml $@
../kibot/report_templates/report_full_svg.txt: ../kibot/report_templates/report_full.txt
sed -e 's/layer_pdfs/layer_svgs/' $< > $@

View File

@ -970,6 +970,8 @@ outputs:
add_background: false
# [string='#FFFFFF'] Color for the background when `add_background` is enabled
background_color: '#FFFFFF'
# [string=''] Background image, must be an SVG, only when `add_background` is enabled
background_image: ''
# [string=''] Color used for blind/buried `colored_vias`
blind_via_color: ''
# [string='_builtin_classic'] Selects the color theme. Only applies to KiCad 6.
@ -1514,7 +1516,8 @@ outputs:
eurocircuits_class_target: '10F'
# [string='%f-%i%I%v.%x'] Output file name (%i='report', %x='txt'). Affected by global options
output: '%f-%i%I%v.%x'
# [string='full'] Name for one of the internal templates (full, simple) or a custom template file
# [string='full'] Name for one of the internal templates (full, full_svg, simple) or a custom template file.
# Note: when converting to PDF PanDoc can fail on some Unicode values (use `simple_ASCII`)
template: 'full'
# Schematic with variant generator:
# This copy isn't intended for development.

View File

@ -12,6 +12,7 @@ Usage:
[-q | -v...] [-i] [-C] [-m MKFILE] [-g DEF]... [TARGET...]
kibot [-v...] [-b BOARD] [-e SCHEMA] [-c PLOT_CONFIG] --list
kibot [-v...] [-b BOARD] [-d OUT_DIR] [-p | -P] --example
kibot [-v...] --quick-start
kibot [-v...] --help-filters
kibot [-v...] --help-global-options
kibot [-v...] --help-list-outputs
@ -87,12 +88,12 @@ if os.environ.get('KIAUS_USE_NIGHTLY'): # pragma: no cover (nightly)
os.environ['PYTHONPATH'] = pcbnew_path
nightly = True
from .gs import (GS)
from .misc import (NO_PCB_FILE, NO_SCH_FILE, EXIT_BAD_ARGS, W_VARSCH, W_VARCFG, W_VARPCB, NO_PCBNEW_MODULE,
W_NOKIVER, hide_stderr)
from .misc import (EXIT_BAD_ARGS, W_VARCFG, NO_PCBNEW_MODULE, W_NOKIVER, hide_stderr)
from .pre_base import (BasePreFlight)
from .config_reader import (CfgYamlReader, print_outputs_help, print_output_help, print_preflights_help, create_example,
print_filters_help, print_global_options_help)
from .kiplot import (generate_outputs, load_actions, config_output, generate_makefile)
from .kiplot import (generate_outputs, load_actions, config_output, generate_makefile, generate_examples, solve_schematic,
solve_board_file, solve_project_file, check_board_file)
GS.kibot_version = __version__
@ -112,59 +113,6 @@ def list_pre_and_outs(logger, outputs):
logger.info('- '+str(o))
def solve_schematic(a_schematic, a_board_file, config):
schematic = a_schematic
if not schematic and a_board_file:
base = os.path.splitext(a_board_file)[0]
sch = base+'.sch'
if os.path.isfile(sch):
schematic = sch
else:
sch = base+'.kicad_sch'
if os.path.isfile(sch):
schematic = sch
if not schematic:
schematics = glob('*.sch')+glob('*.kicad_sch')
if len(schematics) == 1:
schematic = schematics[0]
logger.info('Using SCH file: '+schematic)
elif len(schematics) > 1:
# Look for a schematic with the same name as the config
if config[0] == '.':
# Unhide hidden config
config = config[1:]
while '.' in config:
config = os.path.splitext(config)[0]
sch = config+'.sch'
if os.path.isfile(sch):
schematic = sch
else:
sch = config+'.kicad_sch'
if os.path.isfile(sch):
schematic = sch
else:
# Look for a schematic with a PCB and/or project
for sch in schematics:
base = os.path.splitext(sch)[0]
if (os.path.isfile(base+'.pro') or os.path.isfile(base+'.kicad_pro') or
os.path.isfile(base+'.kicad_pcb')):
schematic = sch
break
else:
schematic = schematics[0]
logger.warning(W_VARSCH + 'More than one SCH file found in current directory.\n'
' Using '+schematic+' if you want to use another use -e option.')
if schematic and not os.path.isfile(schematic):
logger.error("Schematic file not found: "+schematic)
sys.exit(NO_SCH_FILE)
if schematic:
schematic = os.path.abspath(schematic)
logger.debug('Using schematic: `{}`'.format(schematic))
else:
logger.debug('No schematic file found')
return schematic
def solve_config(a_plot_config):
plot_config = a_plot_config
if not plot_config:
@ -186,35 +134,6 @@ def solve_config(a_plot_config):
return plot_config
def check_board_file(board_file):
if board_file and not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
sys.exit(NO_PCB_FILE)
def solve_board_file(schematic, a_board_file):
board_file = a_board_file
if not board_file and schematic:
pcb = os.path.splitext(schematic)[0]+'.kicad_pcb'
if os.path.isfile(pcb):
board_file = pcb
if not board_file:
board_files = glob('*.kicad_pcb')
if len(board_files) == 1:
board_file = board_files[0]
logger.info('Using PCB file: '+board_file)
elif len(board_files) > 1:
board_file = board_files[0]
logger.warning(W_VARPCB + 'More than one PCB file found in current directory.\n'
' Using '+board_file+' if you want to use another use -b option.')
check_board_file(board_file)
if board_file:
logger.debug('Using PCB: `{}`'.format(board_file))
else:
logger.debug('No PCB file found')
return board_file
def set_locale():
""" Try to set the locale for all the cataegories.
If it fails try with LC_NUMERIC (the one we need for tests). """
@ -305,18 +224,6 @@ def detect_kicad():
logger.debug('KiCad config path {}'.format(GS.kicad_conf_path))
def solve_project_file():
if GS.pcb_file:
pro_name = GS.pcb_no_ext+GS.pro_ext
if os.path.isfile(pro_name):
return pro_name
if GS.sch_file:
pro_name = GS.sch_no_ext+GS.pro_ext
if os.path.isfile(pro_name):
return pro_name
return None
def main():
set_locale()
ver = 'KiBot '+__version__+' - '+__copyright__+' - License: '+__license__
@ -366,13 +273,17 @@ def main():
sys.exit(EXIT_BAD_ARGS)
create_example(args.board_file, GS.out_dir, args.copy_options, args.copy_and_expand)
sys.exit(0)
if args.quick_start:
# Some kind of wizard to get usable examples
generate_examples()
sys.exit(0)
# Determine the YAML file
plot_config = solve_config(args.plot_config)
# Determine the SCH file
GS.set_sch(solve_schematic(args.schematic, args.board_file, plot_config))
GS.set_sch(solve_schematic('.', args.schematic, args.board_file, plot_config))
# Determine the PCB file
GS.set_pcb(solve_board_file(GS.sch_file, args.board_file))
GS.set_pcb(solve_board_file('.', args.board_file))
# Determine the project file
GS.set_pro(solve_project_file())

View File

@ -0,0 +1,65 @@
# MacroFab compatible XYRS
# https://help.macrofab.com/knowledge/macrofab-required-design-files
kibot:
version: 1
filters:
- name: fix_rotation
comment: 'Adjust rotation for JLC'
type: rot_footprint
- name: only_smd
comment: 'Only SMD parts'
type: generic
exclude_virtual: true
exclude_tht: true
variants:
- name: rotated
comment: 'Just a place holder for the rotation filter'
type: kibom
variant: rotated
pre_transform: fix_rotation
dnf_filter: only_smd
outputs:
- name: 'macrofab_xyrs'
comment: "Pick and place file, XYRS style"
type: bom
options:
variant: rotated
output: '%f_MacroFab.XYRS'
units: mils
group_fields: []
sort_style: ref
use_aux_axis_as_origin: true
ignore_dnf: false
footprint_populate_values: '0,1'
footprint_type_values: '1,2,0'
csv:
separator: ' '
hide_pcb_info: true
hide_stats_info: true
hide_header: true
columns:
- field: References
name: Designator
- field: Footprint X
name: X-Loc
- field: Footprint Y
name: Y-Loc
- field: Footprint Rot
name: Rotation
- field: Footprint Side
name: Side
- field: Footprint Type
name: Type
- field: Footprint X-Size
name: X-Size
- field: Footprint Y-Size
name: Y-Size
- field: Value
- field: Footprint
- field: Footprint Populate
name: Populate
- field: MPN

View File

@ -0,0 +1,59 @@
# Gerber and drill files for Elecrow, without stencil
# URL: https://www.elecrow.com/
# Based on setting used by Gerber Zipper (https://github.com/g200kg/kicad-gerberzipper)
kibot:
version: 1
outputs:
- name: Elecrow_gerbers
comment: Gerbers compatible with Elecrow
type: gerber
dir: Elecrow
options: &gerber_options
exclude_edge_layer: true
exclude_pads_from_silkscreen: true
plot_sheet_reference: false
plot_footprint_refs: true
plot_footprint_values: true
force_plot_invisible_refs_vals: false
tent_vias: true
use_protel_extensions: true
create_gerber_job_file: false
output: "%f.%x"
gerber_precision: 4.6
use_gerber_x2_attributes: false
use_gerber_net_attributes: false
disable_aperture_macros: true
line_width: 0.1
uppercase_extensions: true
subtract_mask_from_silk: true
inner_extension_pattern: '.g%n'
edge_cut_extension: '.gml'
layers:
- copper
- F.SilkS
- B.SilkS
- F.Mask
- B.Mask
- Edge.Cuts
- name: Elecrow_drill
comment: Drill files compatible with Elecrow
type: excellon
dir: Elecrow
options:
pth_and_npth_single_file: false
pth_id: ''
npth_id: '-NPTH'
output: "%f%i.TXT"
- name: Elecrow
comment: ZIP file for Elecrow
type: compress
dir: Elecrow
options:
files:
- from_output: Elecrow_gerbers
dest: /
- from_output: Elecrow_drill
dest: /

View File

@ -0,0 +1,59 @@
# Gerber and drill files for FusionPCB, without stencil
# URL: https://www.seeedstudio.io/fusion.html
# Based on setting used by Gerber Zipper (https://github.com/g200kg/kicad-gerberzipper)
kibot:
version: 1
outputs:
- name: FusionPCB_gerbers
comment: Gerbers compatible with FusionPCB
type: gerber
dir: FusionPCB
options: &gerber_options
exclude_edge_layer: true
exclude_pads_from_silkscreen: true
plot_sheet_reference: false
plot_footprint_refs: true
plot_footprint_values: true
force_plot_invisible_refs_vals: false
tent_vias: true
use_protel_extensions: true
create_gerber_job_file: false
output: "%f.%x"
gerber_precision: 4.6
use_gerber_x2_attributes: false
use_gerber_net_attributes: false
disable_aperture_macros: true
line_width: 0.1
uppercase_extensions: true
subtract_mask_from_silk: false
use_aux_axis_as_origin: true
inner_extension_pattern: '.gl%N'
edge_cut_extension: '.gml'
layers:
- copper
- F.SilkS
- B.SilkS
- F.Mask
- B.Mask
- Edge.Cuts
- name: FusionPCB_drill
comment: Drill files compatible with FusionPCB
type: excellon
dir: FusionPCB
options:
pth_and_npth_single_file: true
use_aux_axis_as_origin: true
output: "%f.TXT"
- name: FusionPCB
comment: ZIP file for FusionPCB
type: compress
dir: FusionPCB
options:
files:
- from_output: FusionPCB_gerbers
dest: /
- from_output: FusionPCB_drill
dest: /

View File

@ -0,0 +1,133 @@
# Gerber and drill files for JLCPCB, without stencil
# URL: https://jlcpcb.com/
# Based on setting used by Gerber Zipper (https://github.com/g200kg/kicad-gerberzipper)
kibot:
version: 1
filters:
- name: only_jlc_parts
comment: 'Only parts with JLC (LCSC) code'
type: generic
include_only:
- column: 'LCSC#'
regex: '^C\d+'
variants:
- name: rotated
comment: 'Just a place holder for the rotation filter'
type: kibom
variant: rotated
pre_transform: _rot_footprint
outputs:
- name: JLCPCB_gerbers
comment: Gerbers compatible with JLCPCB
type: gerber
dir: JLCPCB
options: &gerber_options
exclude_edge_layer: true
exclude_pads_from_silkscreen: true
plot_sheet_reference: false
plot_footprint_refs: true
plot_footprint_values: false
force_plot_invisible_refs_vals: false
tent_vias: true
use_protel_extensions: true
create_gerber_job_file: false
disable_aperture_macros: true
gerber_precision: 4.6
use_gerber_x2_attributes: false
use_gerber_net_attributes: false
line_width: 0.1
subtract_mask_from_silk: true
inner_extension_pattern: '.gp%n'
layers:
# Note: a more generic approach is to use 'copper' but then the filenames
# are slightly different.
- F.Cu
- B.Cu
- In1.Cu
- In2.Cu
- In3.Cu
- In4.Cu
- In5.Cu
- In6.Cu
- F.SilkS
- B.SilkS
- F.Mask
- B.Mask
- Edge.Cuts
- name: JLCPCB_drill
comment: Drill files compatible with JLCPCB
type: excellon
dir: JLCPCB
options:
pth_and_npth_single_file: false
pth_id: '-PTH'
npth_id: '-NPTH'
metric_units: true
map: gerber
route_mode_for_oval_holes: false
output: "%f%i.%x"
- name: 'JLCPCB_position'
comment: "Pick and place file, JLCPCB style"
type: position
dir: JLCPCB
options:
variant: rotated
output: '%f_cpl_jlc.%x'
format: CSV
units: millimeters
separate_files_for_front_and_back: false
only_smd: true
columns:
- id: Ref
name: Designator
- Val
- Package
- id: PosX
name: "Mid X"
- id: PosY
name: "Mid Y"
- id: Rot
name: Rotation
- id: Side
name: Layer
- name: 'JLCPCB_bom'
comment: "BoM for JLCPCB"
type: bom
dir: JLCPCB
options:
output: '%f_%i_jlc.%x'
exclude_filter: 'only_jlc_parts'
ref_separator: ','
columns:
- field: Value
name: Comment
- field: References
name: Designator
- Footprint
- field: 'LCSC#'
name: 'LCSC Part #'
csv:
hide_pcb_info: true
hide_stats_info: true
quote_all: true
- name: JLCPCB
comment: ZIP file for JLCPCB
type: compress
dir: JLCPCB
options:
files:
- from_output: JLCPCB_gerbers
dest: /
- from_output: JLCPCB_drill
dest: /
- from_output: JLCPCB_position
dest: /
- from_output: JLCPCB_bom
dest: /

View File

@ -0,0 +1,69 @@
# Gerber and drill files for PCBWay, with stencil (solder paste)
# URL: https://www.pcbway.com
# Based on setting used by Gerber Zipper (https://github.com/g200kg/kicad-gerberzipper)
kibot:
version: 1
outputs:
- name: PCBWay_gerbers
comment: Gerbers compatible with PCBWay
type: gerber
dir: PCBWay
options: &gerber_options
exclude_edge_layer: true
exclude_pads_from_silkscreen: true
plot_sheet_reference: false
plot_footprint_refs: true
plot_footprint_values: true
force_plot_invisible_refs_vals: false
tent_vias: true
use_protel_extensions: true
create_gerber_job_file: false
output: "%f.%x"
gerber_precision: 4.6
use_gerber_x2_attributes: false
use_gerber_net_attributes: false
disable_aperture_macros: true
line_width: 0.1
subtract_mask_from_silk: false
inner_extension_pattern: '.gl%N'
layers:
- copper
- F.SilkS
- B.SilkS
- F.Mask
- B.Mask
- F.Paste
- B.Paste
- Edge.Cuts
- name: PCBWay_drill
comment: Drill files compatible with PCBWay
type: excellon
dir: PCBWay
options:
metric_units: false
minimal_header: true
zeros_format: SUPPRESS_LEADING
# left_digits: 3
# right_digits: 3
# See https://github.com/INTI-CMNB/kicad-ci-test-spora/issues/1
# and https://docs.oshpark.com/design-tools/gerbv/fix-drill-format/
left_digits: 2
right_digits: 4
pth_and_npth_single_file: false
pth_id: ''
npth_id: '-NPTH'
output: "%f%i.drl"
- name: PCBWay
comment: ZIP file for PCBWay
type: compress
dir: PCBWay
options:
format: ZIP
files:
- from_output: PCBWay_gerbers
dest: /
- from_output: PCBWay_drill
dest: /

View File

@ -350,3 +350,16 @@ class GS(object):
def load_sch():
""" Will be repplaced by kiplot.py """
raise AssertionError()
@staticmethod
def get_useful_layers(useful, layers, include_copper=False):
""" Filters layers selecting the ones from useful """
from .layer import Layer
if include_copper:
# This is a list of layers that we could add
useful = {la._id for la in Layer.solve(useful)}
# Now filter the list of layers using the ones we are interested on
return [la for la in layers if (include_copper and la.is_copper()) or la._id in useful]
# Similar but keeping the sorting of useful
use = {la._id for la in layers}
return [la for la in Layer.solve(useful) if la._id in use]

View File

@ -11,6 +11,7 @@ Main KiBot code
import os
import re
import yaml
from sys import exit
from sys import path as sys_path
from shutil import which
@ -25,7 +26,7 @@ from .registrable import RegOutput
from .misc import (PLOT_ERROR, MISSING_TOOL, CMD_EESCHEMA_DO, URL_EESCHEMA_DO, CORRUPTED_PCB,
EXIT_BAD_ARGS, CORRUPTED_SCH, EXIT_BAD_CONFIG, WRONG_INSTALL, UI_SMD, UI_VIRTUAL,
MOD_SMD, MOD_THROUGH_HOLE, MOD_VIRTUAL, W_PCBNOSCH, W_NONEEDSKIP, W_WRONGCHAR, name2make, W_TIMEOUT,
W_KIAUTO)
W_KIAUTO, W_VARSCH, NO_SCH_FILE, NO_PCB_FILE, W_VARPCB)
from .error import PlotError, KiPlotConfigurationError, config_error, trace_dump
from .pre_base import BasePreFlight
from .kicad.v5_sch import Schematic, SchFileError, SchError
@ -564,6 +565,211 @@ def generate_makefile(makefile, cfg_file, outputs, kibot_sys=False):
f.write('.PHONY: '+' '.join(extra_targets+list(targets.keys()))+'\n')
def solve_schematic(base_dir, a_schematic=None, a_board_file=None, config=None):
schematic = a_schematic
if not schematic and a_board_file:
base = os.path.splitext(a_board_file)[0]
sch = os.path.join(base_dir, base+'.sch')
if os.path.isfile(sch):
schematic = sch
else:
sch = os.path.join(base_dir, base+'.kicad_sch')
if os.path.isfile(sch):
schematic = sch
if not schematic:
schematics = glob(os.path.join(base_dir, '*.sch'))+glob(os.path.join(base_dir, '*.kicad_sch'))
if len(schematics) == 1:
schematic = schematics[0]
logger.info('Using SCH file: '+schematic)
elif len(schematics) > 1:
# Look for a schematic with the same name as the config
if config:
if config[0] == '.':
# Unhide hidden config
config = config[1:]
# Remove any extension
while '.' in config:
config = os.path.splitext(config)[0]
# Try KiCad 5
sch = os.path.join(base_dir, config+'.sch')
if os.path.isfile(sch):
schematic = sch
else:
# Try KiCad 6
sch = os.path.join(base_dir, config+'.kicad_sch')
if os.path.isfile(sch):
schematic = sch
if not schematic:
# Look for a schematic with a PCB and/or project
for sch in schematics:
base = os.path.splitext(sch)[0]
if (os.path.isfile(os.path.join(base_dir, base+'.pro')) or
os.path.isfile(os.path.join(base_dir, base+'.kicad_pro')) or
os.path.isfile(os.path.join(base_dir, base+'.kicad_pcb'))):
schematic = sch
break
else:
# No way to select one, just take the first
schematic = schematics[0]
logger.warning(W_VARSCH + 'More than one SCH file found in current directory.\n'
' Using '+schematic+' if you want to use another use -e option.')
if schematic and not os.path.isfile(schematic):
logger.error("Schematic file not found: "+schematic)
exit(NO_SCH_FILE)
if schematic:
schematic = os.path.abspath(schematic)
logger.debug('Using schematic: `{}`'.format(schematic))
else:
logger.debug('No schematic file found')
return schematic
def check_board_file(board_file):
if board_file and not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
exit(NO_PCB_FILE)
def solve_board_file(base_dir, a_board_file=None):
schematic = GS.sch_file
board_file = a_board_file
if not board_file and schematic:
pcb = os.path.join(base_dir, os.path.splitext(schematic)[0]+'.kicad_pcb')
if os.path.isfile(pcb):
board_file = pcb
if not board_file:
board_files = glob(os.path.join(base_dir, '*.kicad_pcb'))
if len(board_files) == 1:
board_file = board_files[0]
logger.info('Using PCB file: '+board_file)
elif len(board_files) > 1:
board_file = board_files[0]
logger.warning(W_VARPCB + 'More than one PCB file found in current directory.\n'
' Using '+board_file+' if you want to use another use -b option.')
check_board_file(board_file)
if board_file:
logger.debug('Using PCB: `{}`'.format(board_file))
else:
logger.debug('No PCB file found')
return board_file
def solve_project_file():
if GS.pcb_file:
pro_name = GS.pcb_no_ext+GS.pro_ext
if os.path.isfile(pro_name):
return pro_name
if GS.sch_file:
pro_name = GS.sch_no_ext+GS.pro_ext
if os.path.isfile(pro_name):
return pro_name
return None
def look_for_used_layers():
layers = set()
components = {}
# Look inside the modules
for m in GS.get_modules():
layer = m.GetLayer()
components[layer] = components.get(layer, 0)+1
for gi in m.GraphicalItems():
layers.add(gi.GetLayer())
for pad in m.Pads():
for id in pad.GetLayerSet().Seq():
layers.add(id)
# All drawings in the PCB
for e in GS.board.GetDrawings():
layers.add(e.GetLayer())
# Zones
for e in list(GS.board.Zones()):
layers.add(e.GetLayer())
# Tracks and vias
via_type = 'VIA' if GS.ki5() else 'PCB_VIA'
for e in GS.board.GetTracks():
if e.GetClass() == via_type:
for id in e.GetLayerSet().Seq():
layers.add(id)
else:
layers.add(e.GetLayer())
# Now filter the pads and vias potential layers
from .layer import Layer
declared_layers = {la._id for la in Layer.solve('all')}
layers = sorted(declared_layers.intersection(layers))
logger.debug('- Detected layers: {}'.format(layers))
layers = Layer.solve(layers)
for la in layers:
la.components = components.get(la._id, 0)
return layers
def generate_examples():
# Set default global options
glb = GS.global_opts_class()
glb.set_tree({})
glb.config(None)
outs = RegOutput.get_registered()
fname = 'kibot_generated.kibot.yaml'
GS.set_sch(solve_schematic('.'))
GS.set_pcb(solve_board_file('.'))
GS.set_pro(solve_project_file())
if not GS.pcb_file and not GS.sch_file:
return
GS.board = None
GS.sch = None
with open(fname, 'wt') as f:
logger.info('Creating {} example configuration'.format(fname))
f.write("# This is a working example.\n")
f.write("# For a more complete reference use `--example`\n")
f.write('kibot:\n version: 1\n\n')
# Outputs
outs = RegOutput.get_registered()
# List of layers
layers = []
if GS.pcb_file:
load_board(GS.pcb_file)
layers = look_for_used_layers()
if GS.sch_file:
load_sch()
# A helper for the JLCPCB stuff
fil = {'name': 'only_jlc_parts'}
fil['comment'] = 'Only parts with JLC (LCSC) code'
fil['type'] = 'generic'
fil['include_only'] = [{'column': 'LCSC#', 'regex': r'^C\d+'}]
f.write(yaml.dump({'filters': [fil]}, sort_keys=False))
f.write('\n')
# A helper for KiCost demo
var = {'name': 'place_holder'}
var['comment'] = 'Just a place holder for pre_transform filters'
var['type'] = 'kicost'
var['pre_transform'] = ['_kicost_rename', '_rot_footprint']
f.write(yaml.dump({'variants': [var]}, sort_keys=False))
f.write('\n')
# All the outputs
outputs = []
for n, cls in OrderedDict(sorted(outs.items())).items():
o = cls()
if not(o.is_pcb() and GS.pcb_file) and not(o.is_sch() and GS.sch_file):
logger.debug('- {}, skipped (PCB: {} SCH: {})'.format(n, o.is_pcb(), o.is_sch()))
continue
# Look for templates
tpls = glob(os.path.join(os.path.dirname(__file__), 'config_templates', n, '*.kibot.yaml'))
if tpls:
# Load the templates
tpl_names = tpls
tpls = [yaml.safe_load(open(t))['outputs'] for t in tpls]
tree = cls.get_conf_examples(n, layers, tpls)
if tree:
logger.debug('- {}, generated'.format(n))
if tpls:
logger.debug(' - Templates: {}'.format(tpl_names))
outputs.extend(tree)
else:
logger.debug('- {}, nothing to do'.format(n))
f.write(yaml.dump({'outputs': outputs}, sort_keys=False))
# To avoid circular dependencies: Optionable needs it, but almost everything needs Optionable
GS.load_board = load_board
GS.load_sch = load_sch

View File

@ -219,6 +219,8 @@ class Layer(Optionable):
raise PlotError("Inner layer `{}` is not valid for this board".format(layer))
layer.fix_protel_ext()
new_vals.append(layer)
elif isinstance(layer, int):
new_vals.append(cls.create_layer(layer))
else: # A string
ext = None
if layer == 'all':
@ -263,10 +265,16 @@ class Layer(Optionable):
@classmethod
def create_layer(cls, name):
layer = cls()
layer.layer = name
if isinstance(name, str):
layer.layer = name
layer._get_layer_id_from_name()
else:
layer._id = name
layer._is_inner = name > pcbnew.F_Cu and name < pcbnew.B_Cu
name = GS.board.GetLayerName(name)
layer.layer = name
layer.suffix = name.replace('.', '_')
layer.description = Layer.DEFAULT_LAYER_DESC.get(name)
layer._get_layer_id_from_name()
layer.description = Layer.DEFAULT_LAYER_DESC.get(name, '')
layer.fix_protel_ext()
layer.clean_suffix()
return layer
@ -310,6 +318,15 @@ class Layer(Optionable):
raise KiPlotConfigurationError("Unknown layer name: `{}`".format(self.layer))
return self._id
def is_copper(self):
return self._id >= pcbnew.F_Cu and self._id <= pcbnew.B_Cu
def is_top(self):
return self._id == pcbnew.F_Cu
def is_bottom(self):
return self._id == pcbnew.B_Cu
def __str__(self):
if hasattr(self, '_id'):
return "{} ({} '{}' {})".format(self.layer, self._id, self.description, self.suffix)

View File

@ -236,6 +236,7 @@ W_WKSVERSION = '(W086) '
W_WRONGOAR = '(W087) '
W_ECCLASST = '(W088) '
W_PDMASKFAIL = '(W089) '
W_MISSTOOL = '(W090) '
# Somehow arbitrary, the colors are real, but can be different
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",

View File

@ -253,5 +253,21 @@ class AnyLayer(BaseOutput):
def get_targets(self, out_dir):
return self.options.get_targets(out_dir, self.layers)
@staticmethod
def layer2dict(la):
return {'layer': la.layer, 'suffix': la.suffix, 'description': la.description}
@staticmethod
def get_conf_examples(name, layers, templates):
gb = {}
outs = [gb]
name_u = name.upper()
gb['name'] = 'basic_'+name
gb['comment'] = 'Individual layers in '+name_u+' format'
gb['type'] = name
gb['dir'] = os.path.join('Individual_Layers', name_u)
gb['layers'] = [AnyLayer.layer2dict(la) for la in layers]
return outs
def run(self, output_dir):
self.options.run(output_dir, self.layers)

View File

@ -56,6 +56,7 @@ class BaseOutput(RegOutput):
self.dir = GS.global_dir
self._sch_related = False
self._both_related = False
self._none_related = False
self._unkown_is_error = True
self._done = False
@ -69,7 +70,7 @@ class BaseOutput(RegOutput):
def is_pcb(self):
""" True for outputs that works on the PCB """
return (not self._sch_related) or self._both_related
return (not(self._sch_related) and not(self._none_related)) or self._both_related
def get_targets(self, out_dir):
""" Returns a list of targets generated by this output """
@ -128,6 +129,20 @@ class BaseOutput(RegOutput):
name = self.options.expand_filename_both(name, is_sch=self._sch_related)
return os.path.abspath(os.path.join(out_dir, name))
@staticmethod
def get_conf_examples(name, layers, templates):
return None
@staticmethod
def simple_conf_examples(name, comment, dir):
gb = {}
outs = [gb]
gb['name'] = 'basic_'+name
gb['comment'] = comment
gb['type'] = name
gb['dir'] = dir
return outs
def run(self, output_dir):
self.output_dir = output_dir
self.options.run(self.expand_filename(output_dir, self.options.output))

View File

@ -178,3 +178,7 @@ class BoardView(BaseOutput): # noqa: F821
with document:
self.options = BoardViewOptions
""" [dict] Options for the `boardview` output """
@staticmethod
def get_conf_examples(name, layers, templates):
return BaseOutput.simple_conf_examples(name, 'Board View export', 'Assembly') # noqa: F821

View File

@ -9,16 +9,19 @@ This is somehow compatible with KiBoM.
"""
import os
import re
from copy import deepcopy
from .gs import GS
from .misc import W_BADFIELD, W_NEEDSPCB
from .misc import W_BADFIELD, W_NEEDSPCB, DISTRIBUTORS
from .optionable import Optionable, BaseOptions
from .registrable import RegOutput
from .error import KiPlotConfigurationError
from .kiplot import get_board_comps_data, load_any_sch
from .bom.columnlist import ColumnList, BoMError
from .bom.bom import do_bom
from .bom.xlsx_writer import KICOST_SUPPORT
from .var_kibom import KiBoM
from .fil_base import BaseFilter, apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, reset_filters
from .fil_base import (BaseFilter, apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, reset_filters,
KICOST_NAME_TRANSLATIONS)
from .macros import macros, document, output_class # noqa: F401
from . import log
# To debug the `with document` we can use:
@ -479,7 +482,8 @@ class BoMOptions(BaseOptions):
def _get_columns():
""" Create a list of valid columns """
if GS.sch:
return (GS.sch.get_field_names(ColumnList.COLUMNS_DEFAULT), ColumnList.COLUMNS_EXTRA)
cols = deepcopy(ColumnList.COLUMNS_DEFAULT)
return (GS.sch.get_field_names(cols), ColumnList.COLUMNS_EXTRA)
return (ColumnList.COLUMNS_DEFAULT, ColumnList.COLUMNS_EXTRA)
def _guess_format(self):
@ -744,3 +748,124 @@ class BoM(BaseOutput): # noqa: F821
self.options = BoMOptions
""" [dict] Options for the `bom` output """
self._sch_related = True
@staticmethod
def create_bom(fmt, subd, group_fields, join_fields, fld_names, cols=None):
gb = {}
gb['name'] = subd.lower()+'_bom_'+fmt.lower()
gb['comment'] = '{} Bill of Materials in {} format'.format(subd, fmt)
gb['type'] = 'bom'
gb['dir'] = os.path.join('BoM', subd)
ops = {'format': fmt}
if group_fields:
ops['group_fields'] = group_fields
if join_fields:
columns = []
for c in fld_names:
if c.lower() == 'value':
columns.append({'field': c, 'join': list(join_fields)})
else:
columns.append(c)
ops['columns'] = columns
if cols:
ops['columns'] = cols
if GS.board:
ops['count_smd_tht'] = True
gb['options'] = ops
return gb
@staticmethod
def process_templates(templates, outs, mpn_fields, dists):
for tpl in templates:
for out in tpl:
if out['type'] == 'bom':
# Use the KiCost + rotate variant
out['options']['variant'] = 'place_holder'
columns = out['options'].get('columns', None)
if columns:
# Rename MPN for something we have, or just remove it
to_remove = None
for c in columns:
fld = c.get('field', '')
if fld.lower() == 'mpn':
if mpn_fields:
c['field'] = 'manf#'
elif dists:
c['field'] = list(dists)[0]+'#'
else:
to_remove = c
if to_remove:
columns.remove(to_remove)
# Currently we have a position example (XYRS)
out['dir'] = 'Position'
outs.append(out)
@staticmethod
def get_conf_examples(name, layers, templates):
outs = []
# Make a list of available fields
fld_names, extra_names = BoMOptions._get_columns()
fld_names_l = [f.lower() for f in fld_names]
fld_set = set(fld_names_l)
logger.debug(' - Available fields {}'.format(fld_names_l))
# Look for the manufaturer part number
mpn_set = {k for k, v in KICOST_NAME_TRANSLATIONS.items() if v == 'manf#'}
mpn_set.add('manf#')
mpn_fields = fld_set.intersection(mpn_set)
# Look for distributor part number
dpn_set = set()
for stub in ['part#', '#', 'p#', 'pn', 'vendor#', 'vp#', 'vpn', 'num']:
for dist in DISTRIBUTORS:
dpn_set.add(dist+stub)
if stub != '#':
dpn_set.add(dist+'_'+stub)
dpn_set.add(dist+'-'+stub)
dpn_fields = fld_set.intersection(dpn_set)
# Collect the used distributors
dists = set()
for dist in DISTRIBUTORS:
for fld in dpn_fields:
if dist in fld:
dists.add(dist)
break
# Add it to the group_fields
xpn_fields = mpn_fields | dpn_fields
group_fields = None
if xpn_fields:
group_fields = GroupFields.get_default().copy()
group_fields.extend(list(xpn_fields))
logger.debug(' - Adding grouping fields {}'.format(xpn_fields))
# Look for fields to join to the value
joinable_set = {'tolerance', 'voltage', 'power', 'current'}
join_fields = fld_set.intersection(joinable_set)
if join_fields:
logger.debug(' - Fields to join with Value: {}'.format(join_fields))
# Create a generic version
for fmt in ['HTML', 'CSV', 'TXT', 'TSV', 'XML', 'XLSX']:
outs.append(BoM.create_bom(fmt, 'Generic', group_fields, join_fields, fld_names))
if GS.board:
# Create an example showing the positional fields
cols = ColumnList.COLUMNS_DEFAULT + ColumnList.COLUMNS_EXTRA
for fmt in ['HTML', 'XLSX']:
gb = BoM.create_bom(fmt, 'Positional', group_fields, None, fld_names, cols)
gb['options'][fmt.lower()] = {'style': 'modern-red'}
outs.append(gb)
# Create a costs version
if KICOST_SUPPORT: # and dists?
logger.debug(' - KiCost distributors {}'.format(dists))
grp = group_fields
if group_fields:
# We will apply KiCost rename rules, so we must use their names
grp = GroupFields.get_default().copy()
if mpn_fields:
grp.append('manf#')
for d in dists:
grp.append(d+'#')
gb = BoM.create_bom('XLSX', 'Costs', grp, join_fields, fld_names)
gb['options']['xlsx'] = {'style': 'modern-green', 'kicost': True, 'specs': True}
gb['options']['variant'] = 'place_holder'
# gb['options']['distributors'] = list(dists)
outs.append(gb)
# Add the list of layers to the templates
BoM.process_templates(templates, outs, mpn_fields, dists)
return outs

View File

@ -17,6 +17,10 @@ logger = log.get_logger()
USER_AGENT = 'Mozilla/5.0 (Windows NT 5.2; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'
def is_url(ds):
return ds.startswith('http://') or ds.startswith('https://')
class Download_Datasheets_Options(VariantOptions):
_vars_regex = re.compile(r'\$\{([^\}]+)\}')
@ -110,7 +114,7 @@ class Download_Datasheets_Options(VariantOptions):
field_used = True
if not c.included or (not c.fitted and not self.dnf):
continue
if ds:
if ds and is_url(ds):
known = self._urls.get(ds, None)
if known is None or self.repeated:
name = self.out_name(c)
@ -150,3 +154,14 @@ class Download_Datasheets(BaseOutput): # noqa: F821
def run(self, output_dir):
# No output member, just a dir
self.options.run(output_dir)
@staticmethod
def get_conf_examples(name, layers, templates):
has_urls = False
for c in GS.sch.get_components():
if c.datasheet and is_url(c.datasheet):
has_urls = True
break
if not has_urls:
return None
return BaseOutput.simple_conf_examples(name, 'Download the datasheets', 'Datasheets') # noqa: F821

View File

@ -55,3 +55,15 @@ class Excellon(BaseOutput): # noqa: F821
with document:
self.options = ExcellonOptions
""" [dict] Options for the `excellon` output """
@staticmethod
def get_conf_examples(name, layers, templates):
gb = {}
outs = [gb]
name_u = name.upper()
gb['name'] = 'basic_'+name
gb['comment'] = 'Drill files in '+name_u+' format'
gb['type'] = name
gb['dir'] = 'Gerbers_and_Drill'
gb['options'] = {'map': 'pdf'}
return outs

View File

@ -71,3 +71,7 @@ class GenCAD(BaseOutput): # noqa: F821
with document:
self.options = GenCADOptions
""" [dict] Options for the `gencad` output """
@staticmethod
def get_conf_examples(name, layers, templates):
return BaseOutput.simple_conf_examples(name, 'PCB in GenCAD format', 'Export') # noqa: F821

View File

@ -32,3 +32,15 @@ class Gerb_Drill(BaseOutput): # noqa: F821
with document:
self.options = Gerb_DrillOptions
""" [dict] Options for the `gerb_drill` output """
@staticmethod
def get_conf_examples(name, layers, templates):
gb = {}
outs = [gb]
name_u = name.upper()
gb['name'] = 'basic_'+name
gb['comment'] = 'Drill files in '+name_u+' format'
gb['type'] = name
gb['dir'] = 'Gerbers_and_Drill'
gb['options'] = {'map': 'gerber'}
return outs

View File

@ -5,11 +5,16 @@
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# Adapted from: https://github.com/johnbeard/kiplot
import os
from pcbnew import (PLOT_FORMAT_GERBER, FromMM, ToMM)
from .gs import GS
from .out_any_layer import (AnyLayer, AnyLayerOptions)
from .error import KiPlotConfigurationError
from .macros import macros, document, output_class # noqa: F401
from . import log
logger = log.get_logger()
USEFUL_LAYERS = ['F.SilkS', 'B.SilkS', 'F.Mask', 'B.Mask', 'F.Paste', 'B.Paste', 'Edge.Cuts']
class GerberOptions(AnyLayerOptions):
@ -101,3 +106,32 @@ class Gerber(AnyLayer):
with document:
self.options = GerberOptions
""" [dict] Options for the `gerber` output """
@staticmethod
def get_conf_examples(name, layers, templates):
gb = {}
outs = [gb]
# Create a generic version
gb['name'] = 'gerber_modern'
gb['comment'] = 'Gerbers in modern format, recommended by the standard'
gb['type'] = 'gerber'
gb['dir'] = 'Gerbers_and_Drill'
gb['layers'] = [AnyLayer.layer2dict(la) for la in layers]
# Process the templates
# Filter the list of layers using the ones we are interested on
useful = GS.get_useful_layers(USEFUL_LAYERS, layers, include_copper=True)
tpl_layers = [AnyLayer.layer2dict(la) for la in useful]
# Add the list of layers to the templates
for tpl in templates:
for out in tpl:
if out['type'] == 'gerber':
out['layers'] = tpl_layers
elif out['type'] == 'position':
out['options']['variant'] = 'place_holder'
if out['type'] == 'compress':
out['dir'] = 'Manufacturers'
out['options']['move_files'] = True
else:
out['dir'] = os.path.join('Manufacturers', out['dir'])
outs.append(out)
return outs

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 Salvador E. Tropea
# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2020-2022 Salvador E. Tropea
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
import os
@ -16,6 +16,12 @@ logger = log.get_logger()
WARNING_MIX = "Avoid using it in conjunction with with IBoM native filtering options"
def check_tool():
tool = search_as_plugin(CMD_IBOM, ['InteractiveHtmlBom', 'InteractiveHtmlBom/InteractiveHtmlBom'])
check_script(tool, URL_IBOM)
return tool
class IBoMOptions(VariantOptions):
def __init__(self):
with document:
@ -136,8 +142,7 @@ class IBoMOptions(VariantOptions):
def run(self, name):
super().run(name)
tool = search_as_plugin(CMD_IBOM, ['InteractiveHtmlBom', 'InteractiveHtmlBom/InteractiveHtmlBom'])
check_script(tool, URL_IBOM)
tool = check_tool()
logger.debug('Doing Interactive BoM')
# Tell ibom we don't want to use the screen
os.environ['INTERACTIVE_HTML_BOM_NO_DISPLAY'] = ''
@ -205,3 +210,14 @@ class IBoM(BaseOutput): # noqa: F821
def get_dependencies(self):
return self.options.get_dependencies()
@staticmethod
def get_conf_examples(name, layers, templates):
enabled = True
try:
check_tool()
except SystemExit:
enabled = False
if not enabled:
return None
return BaseOutput.simple_conf_examples(name, 'Interactive HTML BoM', 'Assembly') # noqa: F821

View File

@ -8,7 +8,8 @@
import re
import os
import subprocess
from pcbnew import B_Cu, F_Cu, FromMM, IsCopperLayer, PLOT_CONTROLLER, PLOT_FORMAT_SVG, wxSize, F_Mask, B_Mask
from pcbnew import (B_Cu, F_Cu, FromMM, IsCopperLayer, PLOT_CONTROLLER, PLOT_FORMAT_SVG, wxSize, F_Mask, B_Mask, ZONE_FILLER,
ZONES)
from shutil import rmtree, which
from tempfile import NamedTemporaryFile, mkdtemp
from .svgutils.transform import fromstring, RectElement, fromfile
@ -23,7 +24,7 @@ from .kicad.config import KiConf
from .kicad.v5_sch import SchError
from .kicad.pcb import PCB
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT, MISSING_TOOL, W_PDMASKFAIL,
KICAD5_SVG_SCALE)
KICAD5_SVG_SCALE, W_MISSTOOL)
from .kiplot import check_script, exec_with_retry, add_extra_options
from .macros import macros, document, output_class # noqa: F401
from .layer import Layer, get_priority
@ -39,6 +40,8 @@ VIATYPE_BLIND_BURIED = 2
VIATYPE_MICROVIA = 1
POLY_FILL_STYLE = ("fill:{0}; fill-opacity:1.0; stroke:{0}; stroke-width:1; stroke-opacity:1; stroke-linecap:round; "
"stroke-linejoin:round;fill-rule:evenodd;")
DRAWING_LAYERS = ['Dwgs.User', 'Cmts.User', 'Eco1.User', 'Eco2.User']
EXTRA_LAYERS = ['F.Fab', 'B.Fab', 'F.CrtYd', 'B.CrtYd']
def _run_command(cmd):
@ -518,6 +521,7 @@ class PCB_PrintOptions(VariantOptions):
moved = []
removed = []
vias = []
zones = ZONES()
wxSize(0, 0)
for m in GS.get_modules():
for gi in m.GraphicalItems():
@ -529,9 +533,10 @@ class PCB_PrintOptions(VariantOptions):
if dr.x:
continue
layers = pad.GetLayerSet()
layers.removeLayer(id)
pad.SetLayerSet(layers)
removed.append(pad)
if layers.Contains(id):
layers.removeLayer(id)
pad.SetLayerSet(layers)
removed.append(pad)
for e in GS.board.GetDrawings():
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
@ -540,6 +545,7 @@ class PCB_PrintOptions(VariantOptions):
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
moved.append(e)
zones.append(e)
via_type = 'VIA' if GS.ki5() else 'PCB_VIA'
for e in GS.board.GetTracks():
if e.GetClass() == via_type:
@ -565,6 +571,8 @@ class PCB_PrintOptions(VariantOptions):
for (via, drill, width) in vias:
via.SetDrill(drill)
via.SetWidth(width)
if len(zones):
ZONE_FILLER(GS.board).Fill(zones)
# Add it to the list
filelist.append((GS.pcb_basename+"-"+suffix+".svg", self.pad_color))
@ -576,6 +584,7 @@ class PCB_PrintOptions(VariantOptions):
moved = []
removed = []
vias = []
zones = ZONES()
wxSize(0, 0)
for m in GS.get_modules():
for gi in m.GraphicalItems():
@ -584,9 +593,10 @@ class PCB_PrintOptions(VariantOptions):
moved.append(gi)
for pad in m.Pads():
layers = pad.GetLayerSet()
layers.removeLayer(id)
pad.SetLayerSet(layers)
removed.append(pad)
if layers.Contains(id):
layers.removeLayer(id)
pad.SetLayerSet(layers)
removed.append(pad)
for e in GS.board.GetDrawings():
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
@ -595,6 +605,7 @@ class PCB_PrintOptions(VariantOptions):
if e.GetLayer() == id:
e.SetLayer(tmp_layer)
moved.append(e)
zones.append(e)
via_type = 'VIA' if GS.ki5() else 'PCB_VIA'
for e in GS.board.GetTracks():
if e.GetClass() == via_type:
@ -640,6 +651,8 @@ class PCB_PrintOptions(VariantOptions):
via.SetWidth(width)
via.SetTopLayer(top)
via.SetBottomLayer(bottom)
if len(zones):
ZONE_FILLER(GS.board).Fill(zones)
# Add it to the list
filelist.append((GS.pcb_basename+"-"+suffix+".svg", via_c))
@ -771,6 +784,7 @@ class PCB_PrintOptions(VariantOptions):
if id >= F_Cu and id <= B_Cu:
if self.colored_pads:
self.plot_pads(la, pc, p, filelist)
return
if self.colored_vias:
self.plot_vias(la, pc, p, filelist, VIATYPE_THROUGH, self.via_color)
self.plot_vias(la, pc, p, filelist, VIATYPE_BLIND_BURIED, self.blind_via_color)
@ -1014,3 +1028,72 @@ class PCB_Print(BaseOutput): # noqa: F821
with document:
self.options = PCB_PrintOptions
""" [dict] Options for the `pcb_print` output """
@staticmethod
def get_conf_examples(name, layers, templates):
outs = []
if len(DRAWING_LAYERS) < 10 and GS.ki6():
DRAWING_LAYERS.extend(['User.'+str(c+1) for c in range(9)])
extra = {la._id for la in Layer.solve(EXTRA_LAYERS)}
disabled = set()
# Check we can use PcbDraw
realistic_solder_mask = which('pcbdraw') is not None
if not realistic_solder_mask:
logger.warning(W_MISSTOOL+'Missing PcbDraw tool, disabling `realistic_solder_mask`')
# Check we can convert SVGs
if which(SVG2PDF) is None:
logger.warning(W_MISSTOOL+'Missing {} tool, disabling most printed formats'.format(SVG2PDF))
disabled |= {'PDF', 'PNG', 'EPS', 'PS'}
# Check we can convert to PS
if which(PDF2PS) is None:
logger.warning(W_MISSTOOL+'Missing {} tool, disabling postscript printed format'.format(PDF2PS))
disabled.add('PS')
# Generate one output for each format
for fmt in ['PDF', 'SVG', 'PNG', 'EPS', 'PS']:
if fmt in disabled:
continue
gb = {}
gb['name'] = 'basic_{}_{}'.format(name, fmt.lower())
gb['comment'] = 'PCB'
gb['type'] = name
gb['dir'] = os.path.join('PCB', fmt)
pages = []
# One page for each Cu layer
for la in layers:
page = None
mirror = False
if la.is_copper():
if la.is_top():
use_layers = ['F.Cu', 'F.Mask', 'F.Paste', 'F.SilkS', 'Edge.Cuts']
elif la.is_bottom():
use_layers = ['B.Cu', 'B.Mask', 'B.Paste', 'B.SilkS', 'Edge.Cuts']
mirror = True
else:
use_layers = [la.layer, 'Edge.Cuts']
useful = GS.get_useful_layers(use_layers+DRAWING_LAYERS, layers)
page = {}
page['layers'] = [{'layer': la.layer} for la in useful]
elif la._id in extra:
useful = GS.get_useful_layers([la, 'Edge.Cuts']+DRAWING_LAYERS, layers)
page = {}
page['layers'] = [{'layer': la.layer} for la in useful]
mirror = la.layer.startswith('B.')
if page:
if mirror:
page['mirror'] = True
if la.description:
page['sheet'] = la.description
if realistic_solder_mask:
# Change the color of the masks
for ly in page['layers']:
if ly['layer'].endswith('.Mask'):
ly['color'] = '#14332440'
pages.append(page)
ops = {'format': fmt, 'pages': pages, 'keep_temporal_files': True}
if fmt in ['PNG', 'SVG']:
ops['add_background'] = True
if not realistic_solder_mask:
ops['realistic_solder_mask'] = False
gb['options'] = ops
outs.append(gb)
return outs

View File

@ -20,6 +20,7 @@ from . import log
logger = log.get_logger()
SVG2PNG = 'rsvg-convert'
CONVERT = 'convert'
MIN_VERSION = '0.6.0'
class PcbDrawStyle(Optionable):
@ -256,7 +257,7 @@ class PcbDrawOptions(VariantOptions):
def run(self, name):
super().run(name)
check_script(PCBDRAW, URL_PCBDRAW, '0.6.0')
check_script(PCBDRAW, URL_PCBDRAW, MIN_VERSION)
# Base command with overwrite
cmd = [PCBDRAW]
# Add user options
@ -331,3 +332,34 @@ class PcbDraw(BaseOutput): # noqa: F821
if isinstance(self.options.style, str) and os.path.isfile(self.options.style):
files.append(self.options.style)
return files
@staticmethod
def get_conf_examples(name, layers, templates):
enabled = True
try:
check_script(PCBDRAW, URL_PCBDRAW, MIN_VERSION)
except SystemExit:
enabled = False
if not enabled:
return None
outs = []
for la in layers:
is_top = la.is_top()
is_bottom = la.is_bottom()
if not is_top and not is_bottom:
continue
id = 'top' if is_top else 'bottom'
for style in ['jlcpcb-green-enig', 'set-blue-enig', 'set-red-hasl']:
style_2 = style.replace('-', '_')
for fmt in ['svg', 'png', 'jpg']:
gb = {}
gb['name'] = 'basic_{}_{}_{}_{}'.format(name, fmt, style_2, id)
gb['comment'] = 'PCB 2D render in {} format, using {} style'.format(fmt.upper(), style)
gb['type'] = name
gb['dir'] = os.path.join('PCB', '2D_render', style_2)
ops = {'style': style, 'format': fmt}
if is_bottom:
ops['bottom'] = True
gb['options'] = ops
outs.append(gb)
return outs

View File

@ -34,3 +34,7 @@ class PDF_SCH_Print(BaseOutput): # noqa: F821
self.options = PDF_SCH_PrintOptions
""" [dict] Options for the `pdf_sch_print` output """
self._sch_related = True
@staticmethod
def get_conf_examples(name, layers, templates):
return BaseOutput.simple_conf_examples(name, 'Schematic in PDF format', 'Schematic') # noqa: F821

View File

@ -147,6 +147,7 @@ class PDFUnite(BaseOutput): # noqa: F821
with document:
self.options = PDFUniteOptions
""" [dict] Options for the `pdfunite` output """
self._none_related = True
def get_dependencies(self):
return self.options.get_dependencies()

View File

@ -300,3 +300,26 @@ class Position(BaseOutput): # noqa: F821
with document:
self.options = PositionOptions
""" [dict] Options for the `position` output """
@staticmethod
def get_conf_examples(name, layers, templates):
outs = []
has_top = False
has_bottom = False
for la in layers:
if la.is_top():
has_top = la.components
elif la.is_bottom():
has_bottom = la.components
for fmt in ['ASCII', 'CSV']:
gb = {}
gb['name'] = 'basic_position_{}'.format(fmt)
gb['comment'] = 'Components position for Pick & Place'
gb['type'] = name
gb['dir'] = 'Position'
ops = {'format': fmt, 'only_smd': False}
if not has_top or not has_bottom:
ops['separate_files_for_front_and_back'] = False
gb['options'] = ops
outs.append(gb)
return outs

View File

@ -199,3 +199,40 @@ class Render_3D(Base3D): # noqa: F821
with document:
self.options = Render3DOptions
""" [dict] Options for the `render_3d` output """
@staticmethod
def get_conf_examples(name, layers, templates):
outs = []
has_top = False
has_bottom = False
for la in layers:
if la.is_top() or la.layer.startswith('F.'):
has_top = True
elif la.is_bottom() or la.layer.startswith('B.'):
has_bottom = True
if has_top:
gb = {}
gb['name'] = 'basic_{}_top'.format(name)
gb['comment'] = '3D view from top'
gb['type'] = name
gb['dir'] = '3D'
gb['options'] = {'ray_tracing': True, 'orthographic': True}
outs.append(gb)
if GS.ki6():
gb = {}
gb['name'] = 'basic_{}_30deg'.format(name)
gb['comment'] = '3D view from 30 degrees'
gb['type'] = name
gb['dir'] = '3D'
gb['output_id'] = '30deg'
gb['options'] = {'ray_tracing': True, 'rotate_x': 3, 'rotate_z': -2}
outs.append(gb)
if has_bottom:
gb = {}
gb['name'] = 'basic_{}_bottom'.format(name)
gb['comment'] = '3D view from bottom'
gb['type'] = name
gb['dir'] = '3D'
gb['options'] = {'ray_tracing': True, 'orthographic': True, 'view': 'bottom'}
outs.append(gb)
return outs

View File

@ -7,10 +7,11 @@ import os
import re
import pcbnew
from subprocess import check_output, STDOUT, CalledProcessError
from shutil import which
from .gs import GS
from .misc import (UI_SMD, UI_VIRTUAL, MOD_THROUGH_HOLE, MOD_SMD, MOD_EXCLUDE_FROM_POS_FILES, PANDOC, MISSING_TOOL,
FAILED_EXECUTE, W_WRONGEXT, W_WRONGOAR, W_ECCLASST)
FAILED_EXECUTE, W_WRONGEXT, W_WRONGOAR, W_ECCLASST, W_MISSTOOL)
from .registrable import RegOutput
from .out_base import BaseOptions
from .error import KiPlotConfigurationError
@ -788,3 +789,29 @@ class Report(BaseOutput): # noqa: F821
with document:
self.options = ReportOptions
""" [dict] Options for the `report` output """
@staticmethod
def get_conf_examples(name, layers, templates):
if which(PANDOC) is None:
logger.warning((W_MISSTOOL+'Missing {} tool, disabling report in PDF format\n'+PANDOC_INSTALL).format(PANDOC))
pandoc = False
else:
pandoc = True
gb = {}
outs = [gb]
gb['name'] = 'report_simple'
gb['comment'] = 'Simple design report'
gb['type'] = name
gb['output_id'] = '_simple'
gb['options'] = {'template': 'simple_ASCII'}
if pandoc:
gb['options']['do_convert'] = True
gb = {}
gb['name'] = 'report_full'
gb['comment'] = 'Full design report'
gb['type'] = name
gb['options'] = {'template': 'full_SVG'}
if pandoc:
gb['options']['do_convert'] = True
outs.append(gb)
return outs

View File

@ -106,7 +106,7 @@ class STEPOptions(Base3DOptions):
@output_class
class STEP(Base3D): # noqa: F821
class STEP(Base3D):
""" STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
Exports the PCB as a 3D model.
This is the most common 3D format for exchange purposes.
@ -116,3 +116,7 @@ class STEP(Base3D): # noqa: F821
with document:
self.options = STEPOptions
""" [dict] Options for the `step` output """
@staticmethod
def get_conf_examples(name, layers, templates):
return Base3D.simple_conf_examples(name, '3D model in STEP format', '3D')

View File

@ -34,3 +34,7 @@ class SVG_SCH_Print(BaseOutput): # noqa: F821
self.options = SVG_SCH_PrintOptions
""" [dict] Options for the `svg_sch_print` output """
self._sch_related = True
@staticmethod
def get_conf_examples(name, layers, templates):
return BaseOutput.simple_conf_examples(name, 'Schematic in SVG format', 'Schematic') # noqa: F821

View File

@ -0,0 +1,123 @@
# PCB
Board size: ${bb_w_mm}x${bb_h_mm} mm (${bb_w_in}x${bb_h_in} inches)
- This is the size of the rectangle that contains the board
- Thickness: ${thickness_mm} mm (${thickness_mils} mils)
- Material: ${pcb_material}
- Finish: ${pcb_finish}
- Layers: ${layers}
- Copper thickness: ${copper_thickness} µm
Solder mask: ${solder_mask}
- Color: ${solder_mask_color_text}
Silk screen: ${silk_screen}
- Color: ${silk_screen_color_text}
#?edge_connector or castellated_pads or edge_plating
Special features:
#?edge_connector or castellated_pads or edge_plating
#?edge_connector
- Edge connector: ${edge_connector}
#?castellated_pads
- Castellated pads
#?edge_plating
- Edge plating
#?stackup
Stackup:
#?stackup and impedance_controlled
#?stackup and impedance_controlled
Impedance controlled: YES
#?stackup
#?stackup
| Name | Type | Color | Thickness | Material | Epsilon_r | Loss tangent |
#?stackup
|----------------------|----------------------|----------|-----------|-----------------|-----------|--------------|
#?stackup
#stackup:| ${%-20s,name} | ${%-20s,type} | ${%-8s,color} | ${%9d,thickness} | ${%-15s,material} | ${%9.1f,epsilon_r} | ${%12.2f,loss_tangent} |
#?stackup
# Important sizes
Clearance: ${clearance_mm} mm (${clearance_mils} mils)
Track width: ${track_mm} mm (${track_mils} mils)
- By design rules: ${track_d_mm} mm (${track_d_mils} mils)
Drill: ${drill_real_mm} mm (${drill_real_mils} mils)
- Vias: ${via_drill_real_mm} mm (${via_drill_real_mils} mils) [Design: ${via_drill_real_d_mm} mm (${via_drill_real_d_mils} mils)]
- Pads: ${pad_drill_real_mm} mm (${pad_drill_real_mils} mils)
- The above values are real drill sizes, they add ${extra_pth_drill_mm} mm (${extra_pth_drill_mils} mils) to plated holes (PTH)
Via: ${via_pad_mm}/${via_drill_mm} mm (${via_pad_mils}/${via_drill_mils} mils)
- By design rules: ${via_pad_d_mm}/${via_drill_d_mm} mm (${via_pad_d_mils}/${via_drill_d_mils} mils)
- Micro via: ${micro_vias} [${uvia_pad_mm}/${uvia_drill_mm} mm (${uvia_pad_mils}/${uvia_drill_mils} mils)]
- Burried/blind via: ${blind_vias}
Outer Annular Ring: ${oar_mm} mm (${oar_mils} mils)
- By design rules: ${oar_d_mm} mm (${oar_d_mils} mils)
Eurocircuits class: ${pattern_class}${drill_class}
# General stats
Components count: (SMD/THT)
- Top: ${top_smd}/${top_tht} (${top_comp_type})
- Bottom: ${bot_smd}/${bot_tht} (${bot_comp_type})
Defined tracks:
#defined_tracks:- ${track_mm} mm (${track_mils} mils)
Used tracks:
#used_tracks:- ${track_mm} mm (${track_mils} mils) (${count}) defined: ${defined}
Defined vias:
#defined_vias:- ${pad_mm}/${drill_mm} mm (${pad_mils}/${drill_mils} mils)
Used vias:
#used_vias:- ${pad_mm}/${drill_mm} mm (${pad_mils}/${drill_mils} mils) (Count: ${count}, Aspect: ${aspect} ${producibility_level}) defined: ${defined}
Holes (excluding vias):
#hole_sizes_no_vias:- ${drill_mm} mm (${drill_mils} mils) (${count})
Oval holes:
#oval_hole_sizes:- ${drill_1_mm}x${drill_2_mm} mm (${drill_1_mils}x${drill_2_mils} mils) (${count})
Drill tools (including vias and computing adjusts and rounding):
#drill_tools:- ${drill_mm} mm (${drill_mils} mils) (${count})
#?schematic_svgs
# Schematic
#?schematic_svgs
#?schematic_svgs
#schematic_svgs:![${comment}](${path}){ width=16.5cm height=11.7cm }${new_line}
#?layer_svgs
# PCB Layers
#?layer_svgs
#?layer_svgs
#layer_svgs:![${comment}](${path}){ width=16.5cm height=11.7cm }${new_line}