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:
parent
43c5f4f12e
commit
56030c5dc9
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/' $< > $@
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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: /
|
||||
|
|
@ -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: /
|
||||
|
|
@ -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: /
|
||||
|
|
@ -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: /
|
||||
13
kibot/gs.py
13
kibot/gs.py
|
|
@ -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]
|
||||
|
|
|
|||
208
kibot/kiplot.py
208
kibot/kiplot.py
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
131
kibot/out_bom.py
131
kibot/out_bom.py
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:{ width=16.5cm height=11.7cm }${new_line}
|
||||
|
||||
|
||||
#?layer_svgs
|
||||
# PCB Layers
|
||||
#?layer_svgs
|
||||
|
||||
#?layer_svgs
|
||||
#layer_svgs:{ width=16.5cm height=11.7cm }${new_line}
|
||||
Loading…
Reference in New Issue