From 3752bcb53e2c3804781bd99153c7650edb7ec456 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Fri, 8 Apr 2022 15:01:10 -0300 Subject: [PATCH] Finished adding all the plot options to `pcb_print` - Now is you can get most of the good things from both: print and plot in the same output. - Is usually much faster than `pdf_pcb_print` --- README.md | 22 ++- docs/samples/generic_plot.kibot.yaml | 42 ++++- kibot/kicad/color_theme.py | 96 ++++++++++ kibot/kicad_colors/_builtin_classic.json | 225 +++++++++++++++++++++++ kibot/kicad_colors/_builtin_default.json | 225 +++++++++++++++++++++++ kibot/misc.py | 2 + kibot/out_pcb_print.py | 204 ++++++++++++-------- tests/yaml_samples/pcb_print.kibot.yaml | 21 ++- 8 files changed, 744 insertions(+), 93 deletions(-) create mode 100644 kibot/kicad/color_theme.py create mode 100644 kibot/kicad_colors/_builtin_classic.json create mode 100644 kibot/kicad_colors/_builtin_default.json diff --git a/README.md b/README.md index fd5d750a..5d3e9726 100644 --- a/README.md +++ b/README.md @@ -1530,23 +1530,41 @@ Next time you need this list just use an alias, like this: - `name`: [string=''] Used to identify this particular output definition. - `options`: [dict] Options for the `pcb_print` output. * Valid keys: + - `color_theme`: [string='_builtin_classic'] Selects the color theme. Only applies to KiCad 6. + To use the KiCad 6 default colors select `_builtin_default`. + Usually user colors are stored as `user`, but you can give it another name. - `dnf_filter`: [string|list(string)='_none'] Name of the filter to mark components as not fitted. A short-cut to use for simple cases where a variant is an overkill. + - `drill_marks`: [string='full'] What to use to indicate the drill places, can be none, small or full (for real scale). - `hide_excluded`: [boolean=false] Hide components in the Fab layer that are marked as excluded by a variant. - `output`: [string='%f-%i%I%v.%x'] Filename for the output PDF (%i=assembly, %x=pdf). Affected by global options. - *output_name*: Alias for output. - `pages`: [list(dict)] List of pages to include in the output document. Each page contains one or more layers of the PCB. * Valid keys: + - `exclude_pads_from_silkscreen`: [boolean=false] Do not plot the component pads in the silk screen (KiCad 5.x only). - `layers`: [list(dict)] List of layers printed in this page. Order is important, the last goes on top. * Valid keys: - `color`: [string=''] Color used for this layer. - `description`: [string=''] A description for the layer, for documentation purposes. + - `force_plot_invisible_refs_vals`: [boolean=false] Include references and values even when they are marked as invisible. - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `plot_footprint_refs`: [boolean=true] Include the footprint references. + - `plot_footprint_values`: [boolean=true] Include the footprint values. - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `line_width`: [number=0.1] [0.02,2] For objects without width [mm] (KiCad 5). - `mirror`: [boolean=false] Print mirrored (X axis inverted). - - `monochrome`: [boolean=false] Print in black and white. - - `sheet_reference_layer`: [string=''] Layer to plot the page frame. + - `monochrome`: [boolean=false] Print in gray scale. + - `negative_plot`: [boolean=false] Invert black and white. Only useful for a single layer. + - `scaling`: [number=1.0] Scale factor (0 means autoscaling). + - `sheet`: [string='Assembly'] Text to use for the `sheet` in the title block. + - `sheet_reference_color`: [string=''] Color to use for the frame and title block. + - `tent_vias`: [boolean=true] Cover the vias. + - `title`: [string=''] Text used to replace the sheet title. %VALUE expansions are allowed. + If it starts with `+` the text is concatenated. + - `plot_sheet_reference`: [boolean=true] Include the title-block. + - `title`: [string=''] Text used to replace the sheet title. %VALUE expansions are allowed. + If it starts with `+` the text is concatenated. - `variant`: [string=''] Board variant to apply. - `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. diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index b0db5c47..0bc90dac 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -957,9 +957,15 @@ outputs: type: 'pcb_print' dir: 'Example/pcb_print_dir' options: + # [string='_builtin_classic'] Selects the color theme. Only applies to KiCad 6. + # To use the KiCad 6 default colors select `_builtin_default`. + # Usually user colors are stored as `user`, but you can give it another name + color_theme: '_builtin_classic' # [string|list(string)='_none'] Name of the filter to mark components as not fitted. # A short-cut to use for simple cases where a variant is an overkill dnf_filter: '_none' + # [string='full'] What to use to indicate the drill places, can be none, small or full (for real scale) + drill_marks: 'full' # [boolean=false] Hide components in the Fab layer that are marked as excluded by a variant hide_excluded: false # [string='%f-%i%I%v.%x'] Filename for the output PDF (%i=assembly, %x=pdf). Affected by global options @@ -968,22 +974,48 @@ outputs: # [list(dict)] List of pages to include in the output document. # Each page contains one or more layers of the PCB pages: - # [list(dict)] List of layers printed in this page. Order is important, the last goes on top - - layers: + # [boolean=false] Do not plot the component pads in the silk screen (KiCad 5.x only) + - exclude_pads_from_silkscreen: false + # [list(dict)] List of layers printed in this page. Order is important, the last goes on top + layers: # [string=''] Color used for this layer - color: '' # [string=''] A description for the layer, for documentation purposes description: '' + # [boolean=false] Include references and values even when they are marked as invisible + force_plot_invisible_refs_vals: false # [string=''] Name of the layer. As you see it in KiCad layer: '' + # [boolean=true] Include the footprint references + plot_footprint_refs: true + # [boolean=true] Include the footprint values + plot_footprint_values: true # [string=''] Suffix used in file names related to this layer. Derived from the name if not specified suffix: '' + # [number=0.1] [0.02,2] For objects without width [mm] (KiCad 5) + line_width: 0.1 # [boolean=false] Print mirrored (X axis inverted) mirror: false - # [boolean=false] Print in black and white + # [boolean=false] Print in gray scale monochrome: false - # [string=''] Layer to plot the page frame - sheet_reference_layer: '' + # [boolean=false] Invert black and white. Only useful for a single layer + negative_plot: false + # [number=1.0] Scale factor (0 means autoscaling) + scaling: 1.0 + # [string='Assembly'] Text to use for the `sheet` in the title block + sheet: 'Assembly' + # [string=''] Color to use for the frame and title block + sheet_reference_color: '' + # [boolean=true] Cover the vias + tent_vias: true + # [string=''] Text used to replace the sheet title. %VALUE expansions are allowed. + # If it starts with `+` the text is concatenated + title: '' + # [boolean=true] Include the title-block + plot_sheet_reference: true + # [string=''] Text used to replace the sheet title. %VALUE expansions are allowed. + # If it starts with `+` the text is concatenated + title: '' # [string=''] Board variant to apply variant: '' # PcbDraw - Beautiful 2D PCB render: diff --git a/kibot/kicad/color_theme.py b/kibot/kicad/color_theme.py new file mode 100644 index 00000000..4fb91540 --- /dev/null +++ b/kibot/kicad/color_theme.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Salvador E. Tropea +# Copyright (c) 2022 Instituto Nacional de TecnologĂ­a Industrial +# License: GPL-3.0 +# Project: KiBot (formerly KiPlot) +""" +KiCad 6 color theme loader +""" +import os +import json +from pcbnew import BOARD, PCBNEW_LAYER_ID_START, PCB_LAYER_ID_COUNT +from ..gs import GS +from ..misc import W_COLORTHEME, W_WRONGCOLOR +from .config import KiConf +from .. import log + +logger = log.get_logger() +BUILT_IN = {'_builtin_classic', '_builtin_default'} +KI6_KI5 = {'b_adhesive': 'b_adhes', + 'f_adhesive': 'f_adhes', + 'b_silkscreen': 'b_silks', + 'f_silkscreen': 'f_silks', + 'user_drawings': 'dwgs_user', + 'user_comments': 'cmts_user', + 'user_eco1': 'eco1_user', + 'user_eco2': 'eco2_user', + 'b_courtyard': 'b_crtyd', + 'f_courtyard': 'f_crtyd'} + + +class KiCadColors(object): + def __init__(self): + self.layer_id2color = {} + self.pcb_frame = "#480000" + + +def parse_color(val): + if val.startswith('rgb('): + vals = val[4:-1].split(',') + elif val.startswith('rgba('): + vals = val[5:-1].split(',') + else: + logger.warning(W_WRONGCOLOR+"Wrong KiCad color: {}".format(val)) + return "#000000" + res = '#' + for c, v in enumerate(vals): + res += '%02X' % (int(v) if c < 3 else int(float(v)*255)) + return res + + +def load_color_theme(name): + logger.debug('Looking for color theme `{}`'.format(name)) + is_built_in = name in BUILT_IN + if not is_built_in and GS.ki5(): + logger.warning(W_COLORTHEME, "KiCad 5 doesn't support color themes ({})".format(name)) + return None + if is_built_in: + fn = os.path.join(os.path.dirname(__file__), '..', 'kicad_colors', name+'.json') + else: + KiConf.init(GS.pcb_file) + fn = os.path.join(KiConf.config_dir, 'colors', name+'.json') + if not os.path.isfile(fn): + logger.warning(W_COLORTHEME, "Missing color theme: {}".format(fn)) + return None + with open(fn, 'rt') as f: + text = f.read() + data = json.loads(text) + c = KiCadColors() + cl = c.layer_id2color + board = data['board'] + copper = board['copper'] + extra_debug = GS.debug_level >= 3 + for id in range(PCBNEW_LAYER_ID_START, PCBNEW_LAYER_ID_START+PCB_LAYER_ID_COUNT): + c_name = c_name_ori = BOARD.GetStandardLayerName(id) + c_name = c_name.lower() + if c_name == 'rescue': + continue + if c_name.endswith('.cu'): + c_name = c_name[:-3] + if c_name in copper: + cl[id] = parse_color(copper[c_name]) + else: + logger.warning(W_WRONGCOLOR+"The `{}` theme doesn't define a color for the {} layer".format(name, c_name_ori)) + else: + c_name = c_name.replace('.', '_') + c_name = KI6_KI5.get(c_name, c_name) + if c_name in board: + cl[id] = parse_color(board[c_name]) + else: + logger.warning(W_WRONGCOLOR+"The `{}` theme doesn't define a color for the {} layer".format(name, c_name_ori)) + if extra_debug: + logger.debug('- Color for layer {} ({}): {}'.format(c_name_ori, id, cl[id])) + # Title block and frame color + if 'worksheet' in board: + c.pcb_frame = parse_color(board['worksheet']) + return c diff --git a/kibot/kicad_colors/_builtin_classic.json b/kibot/kicad_colors/_builtin_classic.json new file mode 100644 index 00000000..b0f88f3c --- /dev/null +++ b/kibot/kicad_colors/_builtin_classic.json @@ -0,0 +1,225 @@ +{ + "3d_viewer": { + "background_bottom": "rgb(102, 102, 128)", + "background_top": "rgb(204, 204, 230)", + "board": "rgba(51, 43, 23, 0.902)", + "copper": "rgb(179, 156, 0)", + "silkscreen_bottom": "rgb(230, 230, 230)", + "silkscreen_top": "rgb(230, 230, 230)", + "soldermask_bottom": "rgba(20, 51, 36, 0.831)", + "soldermask_top": "rgba(20, 51, 36, 0.831)", + "solderpaste": "rgb(128, 128, 128)", + "use_board_stackup_colors": true + }, + "board": { + "anchor": "rgb(0, 0, 132)", + "aux_items": "rgb(255, 255, 255)", + "b_adhes": "rgb(0, 0, 132)", + "b_crtyd": "rgb(132, 132, 132)", + "b_fab": "rgb(0, 0, 132)", + "b_mask": "rgb(132, 132, 0)", + "b_paste": "rgb(0, 194, 194)", + "b_silks": "rgb(132, 0, 132)", + "background": "rgb(0, 0, 0)", + "cmts_user": "rgb(0, 0, 132)", + "copper": { + "b": "rgb(0, 132, 0)", + "f": "rgb(132, 0, 0)", + "in1": "rgb(194, 194, 0)", + "in10": "rgb(132, 0, 132)", + "in11": "rgb(132, 0, 0)", + "in12": "rgb(132, 132, 0)", + "in13": "rgb(194, 194, 194)", + "in14": "rgb(0, 0, 132)", + "in15": "rgb(0, 132, 0)", + "in16": "rgb(132, 0, 0)", + "in17": "rgb(194, 194, 0)", + "in18": "rgb(194, 0, 194)", + "in19": "rgb(194, 0, 0)", + "in2": "rgb(194, 0, 194)", + "in20": "rgb(0, 132, 132)", + "in21": "rgb(0, 132, 0)", + "in22": "rgb(0, 0, 132)", + "in23": "rgb(132, 132, 132)", + "in24": "rgb(132, 0, 132)", + "in25": "rgb(194, 194, 194)", + "in26": "rgb(132, 0, 132)", + "in27": "rgb(132, 0, 0)", + "in28": "rgb(132, 132, 0)", + "in29": "rgb(194, 194, 194)", + "in3": "rgb(194, 0, 0)", + "in30": "rgb(0, 0, 132)", + "in4": "rgb(0, 132, 132)", + "in5": "rgb(0, 132, 0)", + "in6": "rgb(0, 0, 132)", + "in7": "rgb(132, 132, 132)", + "in8": "rgb(132, 0, 132)", + "in9": "rgb(194, 194, 194)" + }, + "cursor": "rgb(255, 255, 255)", + "drc_error": "rgba(255, 0, 0, 0.800)", + "drc_exclusion": "rgba(255, 255, 255, 0.800)", + "drc_warning": "rgba(255, 208, 66, 0.800)", + "dwgs_user": "rgb(194, 194, 194)", + "eco1_user": "rgb(0, 132, 0)", + "eco2_user": "rgb(194, 194, 0)", + "edge_cuts": "rgb(194, 194, 0)", + "f_adhes": "rgb(132, 0, 132)", + "f_crtyd": "rgb(194, 194, 194)", + "f_fab": "rgb(132, 132, 132)", + "f_mask": "rgb(132, 0, 132)", + "f_paste": "rgb(132, 0, 0)", + "f_silks": "rgb(0, 132, 132)", + "footprint_text_invisible": "rgb(194, 194, 194)", + "grid": "rgb(132, 132, 132)", + "grid_axes": "rgb(0, 0, 132)", + "margin": "rgb(194, 0, 194)", + "no_connect": "rgb(0, 0, 132)", + "pad_plated_hole": "rgb(194, 194, 0)", + "pad_through_hole": "rgb(194, 194, 0)", + "plated_hole": "rgb(194, 194, 0)", + "ratsnest": "rgb(255, 255, 255)", + "user_1": "rgb(0, 0, 132)", + "user_2": "rgb(0, 0, 132)", + "user_3": "rgb(0, 0, 132)", + "user_4": "rgb(0, 0, 132)", + "user_5": "rgb(0, 0, 132)", + "user_6": "rgb(0, 0, 132)", + "user_7": "rgb(0, 0, 132)", + "user_8": "rgb(0, 0, 132)", + "user_9": "rgb(0, 0, 132)", + "via_blind_buried": "rgb(132, 132, 0)", + "via_hole": "rgba(128, 102, 0, 0.800)", + "via_micro": "rgb(0, 132, 132)", + "via_through": "rgb(194, 194, 194)", + "worksheet": "rgb(72, 0, 0)" + }, + "gerbview": { + "axes": "rgb(0, 0, 132)", + "background": "rgb(0, 0, 0)", + "dcodes": "rgb(255, 255, 255)", + "grid": "rgb(132, 132, 132)", + "layers": [ + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)" + ], + "negative_objects": "rgb(132, 132, 132)", + "worksheet": "rgb(0, 0, 132)" + }, + "meta": { + "name": "A", + "version": 3 + }, + "palette": [ + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)" + ], + "schematic": { + "anchor": "rgb(0, 0, 255)", + "aux_items": "rgb(0, 0, 0)", + "background": "rgb(245, 244, 239)", + "brightened": "rgb(255, 0, 255)", + "bus": "rgb(0, 0, 132)", + "bus_junction": "rgb(0, 0, 132)", + "component_body": "rgb(255, 255, 194)", + "component_outline": "rgb(132, 0, 0)", + "cursor": "rgb(15, 15, 15)", + "erc_error": "rgba(230, 9, 13, 0.800)", + "erc_warning": "rgba(209, 146, 0, 0.800)", + "fields": "rgb(132, 0, 132)", + "grid": "rgb(181, 181, 181)", + "grid_axes": "rgb(0, 0, 132)", + "hidden": "rgb(94, 194, 194)", + "junction": "rgb(0, 150, 0)", + "label_global": "rgb(132, 0, 0)", + "label_hier": "rgb(114, 86, 0)", + "label_local": "rgb(15, 15, 15)", + "no_connect": "rgb(0, 0, 132)", + "note": "rgb(0, 0, 194)", + "override_item_colors": false, + "pin": "rgb(132, 0, 0)", + "pin_name": "rgb(0, 100, 100)", + "pin_number": "rgb(169, 0, 0)", + "reference": "rgb(0, 100, 100)", + "shadow": "rgba(102, 179, 255, 0.800)", + "sheet": "rgb(132, 0, 0)", + "sheet_background": "rgba(255, 255, 255, 0.000)", + "sheet_fields": "rgb(132, 0, 132)", + "sheet_filename": "rgb(114, 86, 0)", + "sheet_label": "rgb(0, 100, 100)", + "sheet_name": "rgb(0, 100, 100)", + "value": "rgb(0, 100, 100)", + "wire": "rgb(0, 150, 0)", + "worksheet": "rgb(132, 0, 0)" + } +} diff --git a/kibot/kicad_colors/_builtin_default.json b/kibot/kicad_colors/_builtin_default.json new file mode 100644 index 00000000..53759dc7 --- /dev/null +++ b/kibot/kicad_colors/_builtin_default.json @@ -0,0 +1,225 @@ +{ + "3d_viewer": { + "background_bottom": "rgb(102, 102, 128)", + "background_top": "rgb(204, 204, 230)", + "board": "rgba(51, 43, 23, 0.902)", + "copper": "rgb(179, 156, 0)", + "silkscreen_bottom": "rgb(230, 230, 230)", + "silkscreen_top": "rgb(230, 230, 230)", + "soldermask_bottom": "rgba(20, 51, 36, 0.831)", + "soldermask_top": "rgba(20, 51, 36, 0.831)", + "solderpaste": "rgb(128, 128, 128)", + "use_board_stackup_colors": true + }, + "board": { + "anchor": "rgb(255, 38, 226)", + "aux_items": "rgb(255, 255, 255)", + "b_adhes": "rgb(0, 0, 132)", + "b_crtyd": "rgb(38, 233, 255)", + "b_fab": "rgb(88, 93, 132)", + "b_mask": "rgba(2, 255, 238, 0.400)", + "b_paste": "rgba(0, 194, 194, 0.902)", + "b_silks": "rgb(232, 178, 167)", + "background": "rgb(0, 16, 35)", + "cmts_user": "rgb(89, 148, 220)", + "copper": { + "b": "rgb(77, 127, 196)", + "f": "rgb(200, 52, 52)", + "in1": "rgb(127, 200, 127)", + "in10": "rgb(237, 124, 51)", + "in11": "rgb(91, 195, 235)", + "in12": "rgb(247, 111, 142)", + "in13": "rgb(167, 165, 198)", + "in14": "rgb(40, 204, 217)", + "in15": "rgb(232, 178, 167)", + "in16": "rgb(242, 237, 161)", + "in17": "rgb(237, 124, 51)", + "in18": "rgb(91, 195, 235)", + "in19": "rgb(247, 111, 142)", + "in2": "rgb(206, 125, 44)", + "in20": "rgb(167, 165, 198)", + "in21": "rgb(40, 204, 217)", + "in22": "rgb(232, 178, 167)", + "in23": "rgb(242, 237, 161)", + "in24": "rgb(237, 124, 51)", + "in25": "rgb(91, 195, 235)", + "in26": "rgb(247, 111, 142)", + "in27": "rgb(167, 165, 198)", + "in28": "rgb(40, 204, 217)", + "in29": "rgb(232, 178, 167)", + "in3": "rgb(79, 203, 203)", + "in30": "rgb(242, 237, 161)", + "in4": "rgb(219, 98, 139)", + "in5": "rgb(167, 165, 198)", + "in6": "rgb(40, 204, 217)", + "in7": "rgb(232, 178, 167)", + "in8": "rgb(242, 237, 161)", + "in9": "rgb(141, 203, 129)" + }, + "cursor": "rgb(255, 255, 255)", + "drc_error": "rgba(215, 91, 107, 0.800)", + "drc_exclusion": "rgba(255, 255, 255, 0.800)", + "drc_warning": "rgba(255, 208, 66, 0.800)", + "dwgs_user": "rgb(194, 194, 194)", + "eco1_user": "rgb(180, 219, 210)", + "eco2_user": "rgb(216, 200, 82)", + "edge_cuts": "rgb(208, 210, 205)", + "f_adhes": "rgb(132, 0, 132)", + "f_crtyd": "rgb(255, 38, 226)", + "f_fab": "rgb(175, 175, 175)", + "f_mask": "rgba(216, 100, 255, 0.400)", + "f_paste": "rgba(180, 160, 154, 0.902)", + "f_silks": "rgb(242, 237, 161)", + "footprint_text_invisible": "rgb(132, 132, 132)", + "grid": "rgb(132, 132, 132)", + "grid_axes": "rgb(194, 194, 194)", + "margin": "rgb(255, 38, 226)", + "no_connect": "rgb(0, 0, 132)", + "pad_plated_hole": "rgb(194, 194, 0)", + "pad_through_hole": "rgb(227, 183, 46)", + "plated_hole": "rgb(26, 196, 210)", + "ratsnest": "rgba(245, 255, 213, 0.702)", + "user_1": "rgb(194, 194, 194)", + "user_2": "rgb(89, 148, 220)", + "user_3": "rgb(180, 219, 210)", + "user_4": "rgb(216, 200, 82)", + "user_5": "rgb(194, 194, 194)", + "user_6": "rgb(89, 148, 220)", + "user_7": "rgb(180, 219, 210)", + "user_8": "rgb(216, 200, 82)", + "user_9": "rgb(232, 178, 167)", + "via_blind_buried": "rgb(187, 151, 38)", + "via_hole": "rgb(227, 183, 46)", + "via_micro": "rgb(0, 132, 132)", + "via_through": "rgb(236, 236, 236)", + "worksheet": "rgb(200, 114, 171)" + }, + "gerbview": { + "axes": "rgb(0, 0, 132)", + "background": "rgb(0, 0, 0)", + "dcodes": "rgb(255, 255, 255)", + "grid": "rgb(132, 132, 132)", + "layers": [ + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)", + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)" + ], + "negative_objects": "rgb(132, 132, 132)", + "worksheet": "rgb(0, 0, 132)" + }, + "meta": { + "name": "B", + "version": 3 + }, + "palette": [ + "rgb(200, 52, 52)", + "rgb(127, 200, 127)", + "rgb(206, 125, 44)", + "rgb(79, 203, 203)", + "rgb(219, 98, 139)", + "rgb(167, 165, 198)", + "rgb(40, 204, 217)", + "rgb(232, 178, 167)", + "rgb(242, 237, 161)", + "rgb(141, 203, 129)", + "rgb(237, 124, 51)", + "rgb(91, 195, 235)", + "rgb(247, 111, 142)", + "rgb(77, 127, 196)" + ], + "schematic": { + "anchor": "rgb(0, 0, 255)", + "aux_items": "rgb(0, 0, 0)", + "background": "rgb(245, 244, 239)", + "brightened": "rgb(255, 0, 255)", + "bus": "rgb(0, 0, 132)", + "bus_junction": "rgb(0, 0, 132)", + "component_body": "rgb(255, 255, 194)", + "component_outline": "rgb(132, 0, 0)", + "cursor": "rgb(15, 15, 15)", + "erc_error": "rgba(230, 9, 13, 0.800)", + "erc_warning": "rgba(209, 146, 0, 0.800)", + "fields": "rgb(132, 0, 132)", + "grid": "rgb(181, 181, 181)", + "grid_axes": "rgb(0, 0, 132)", + "hidden": "rgb(94, 194, 194)", + "junction": "rgb(0, 150, 0)", + "label_global": "rgb(132, 0, 0)", + "label_hier": "rgb(114, 86, 0)", + "label_local": "rgb(15, 15, 15)", + "no_connect": "rgb(0, 0, 132)", + "note": "rgb(0, 0, 194)", + "override_item_colors": false, + "pin": "rgb(132, 0, 0)", + "pin_name": "rgb(0, 100, 100)", + "pin_number": "rgb(169, 0, 0)", + "reference": "rgb(0, 100, 100)", + "shadow": "rgba(102, 179, 255, 0.800)", + "sheet": "rgb(132, 0, 0)", + "sheet_background": "rgba(255, 255, 255, 0.000)", + "sheet_fields": "rgb(132, 0, 132)", + "sheet_filename": "rgb(114, 86, 0)", + "sheet_label": "rgb(0, 100, 100)", + "sheet_name": "rgb(0, 100, 100)", + "value": "rgb(0, 100, 100)", + "wire": "rgb(0, 150, 0)", + "worksheet": "rgb(132, 0, 0)" + } +} diff --git a/kibot/misc.py b/kibot/misc.py index cd14d1a2..e9884cfa 100644 --- a/kibot/misc.py +++ b/kibot/misc.py @@ -230,6 +230,8 @@ W_NOTPDF = '(W080) ' W_NOREF = '(W081) ' W_UNKVAR = '(W082) ' W_WRONGEXT = '(W083) ' +W_COLORTHEME = '(W084) ' +W_WRONGCOLOR = '(W085) ' # 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", diff --git a/kibot/out_pcb_print.py b/kibot/out_pcb_print.py index 6666d87e..ff3a69ff 100644 --- a/kibot/out_pcb_print.py +++ b/kibot/out_pcb_print.py @@ -7,13 +7,14 @@ # Adapted from: https://gitlab.com/dennevi/Board2Pdf/ # Note: Original code released as Public Domain import os -from pcbnew import PLOT_CONTROLLER, IsCopperLayer, PLOT_FORMAT_PDF +from pcbnew import PLOT_CONTROLLER, PLOT_FORMAT_PDF, FromMM from shutil import rmtree from tempfile import mkdtemp from .error import KiPlotConfigurationError from .gs import GS from .optionable import Optionable from .out_base import VariantOptions +from .kicad.color_theme import load_color_theme from .macros import macros, document, output_class # noqa: F401 from .layer import Layer from . import PyPDF2 @@ -22,29 +23,21 @@ from . import log logger = log.get_logger() # TODO: -# - Se pueden sacar los colores del esquema de colores? # - Opciones de out_pdf y out_any_layer -# - Estas cosas: -# self.scaling = 1.0 -# """ Scale factor (0 means autoscaling)""" -# self._drill_marks = 'full' -# """ What to use to indicate the drill places, can be none, small or full (for real scale) """ -# self.title = '' -# """ Text used to replace the sheet title. %VALUE expansions are allowed. -# If it starts with `+` the text is concatenated """ -# self.color_theme = '_builtin_classic' -# """ Selects the color theme. Onlyu applies to KiCad 6. -# To use the KiCad 6 default colors select `_builtin_default`. -# Usually user colors are stored as `user`, but you can give it another name """ def hex_to_rgb(value): """ Return (red, green, blue) in float between 0-1 for the color given as #rrggbb. """ value = value.lstrip('#') - lv = len(value) - rgb = tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + rgb = tuple(int(value[i:i+2], 16) for i in range(0, 6, 2)) rgb = (rgb[0]/255, rgb[1]/255, rgb[2]/255) - return rgb + alpha = int(value[6:], 16)/255 if len(value) == 8 else 1.0 + return rgb, alpha + + +def to_gray(color): + avg = (color[0]+color[1]+color[2])/3 + return (avg, avg, avg) def colorize_pdf(folder, in_file, out_file, color): @@ -89,7 +82,10 @@ def merge_pdf(input_folder, input_files, output_folder, output_file): i = 0 er = None open_files = [] + extra_debug = GS.debug_level >= 3 for filename in input_files: + if extra_debug: + logger.debug(" - {}".format(filename)) try: file = open(os.path.join(input_folder, filename), 'rb') open_files.append(file) @@ -156,6 +152,19 @@ def create_pdf_from_pages(input_folder, input_files, output_fn): f.close() +def colorize_layer(suffix, color, monochrome, filelist, temp_dir): + in_file = GS.pcb_basename+"-"+suffix+".pdf" + if color != "#000000": + out_file = GS.pcb_basename+"-"+suffix+"-colored.pdf" + logger.debug('- Giving color to {} -> {} ({})'.format(in_file, out_file, color)) + rgb, alpha = hex_to_rgb(color) + color = rgb if not monochrome else to_gray(rgb) + colorize_pdf(temp_dir, in_file, out_file, color) + filelist.append(out_file) + else: + filelist.append(in_file) + + class LayerOptions(Layer): """ Data for a layer """ def __init__(self): @@ -164,10 +173,17 @@ class LayerOptions(Layer): with document: self.color = "" """ Color used for this layer """ + self.plot_footprint_refs = True + """ Include the footprint references """ + self.plot_footprint_values = True + """ Include the footprint values """ + self.force_plot_invisible_refs_vals = False + """ Include references and values even when they are marked as invisible """ def config(self, parent): super().config(parent) - self.validate_color('color') + if self.color: + self.validate_color('color') class PagesOptions(Optionable): @@ -179,9 +195,24 @@ class PagesOptions(Optionable): self.mirror = False """ Print mirrored (X axis inverted) """ self.monochrome = False - """ Print in black and white """ - self.sheet_reference_layer = '' - """ Layer to plot the page frame """ + """ Print in gray scale """ + self.scaling = 1.0 + """ Scale factor (0 means autoscaling)""" + self.title = '' + """ Text used to replace the sheet title. %VALUE expansions are allowed. + If it starts with `+` the text is concatenated """ + self.sheet = 'Assembly' + """ Text to use for the `sheet` in the title block """ + self.sheet_reference_color = '' + """ Color to use for the frame and title block """ + self.line_width = 0.1 + """ [0.02,2] For objects without width [mm] (KiCad 5) """ + self.negative_plot = False + """ Invert black and white. Only useful for a single layer """ + self.exclude_pads_from_silkscreen = False + """ Do not plot the component pads in the silk screen (KiCad 5.x only) """ + self.tent_vias = True + """ Cover the vias """ self.layers = LayerOptions """ [list(dict)] List of layers printed in this page. Order is important, the last goes on top """ @@ -189,18 +220,10 @@ class PagesOptions(Optionable): super().config(parent) if isinstance(self.layers, type): raise KiPlotConfigurationError("Missing `layers` list") + # Fill the ID member for all the layers self.layers = Layer.solve(self.layers) - if self.sheet_reference_layer: - # Layer name to layer ID - layer = Layer() - name = self.sheet_reference_layer - layer.layer = self.sheet_reference_layer - self.sheet_reference_layer = layer._get_layer_id_from_name() - # Check this is one of the specified layers - layer = next(filter(lambda x: x._id == self.sheet_reference_layer, self.layers), None) - if layer is None: - raise KiPlotConfigurationError("The layer selected for the sheet reference ({}) isn't in the list of layers". - format(name)) + if self.sheet_reference_color: + self.validate_color('sheet_reference_color') class PCB_PrintOptions(VariantOptions): @@ -215,29 +238,51 @@ class PCB_PrintOptions(VariantOptions): """ Filename for the output PDF (%i=assembly, %x=pdf)""" self.hide_excluded = False """ Hide components in the Fab layer that are marked as excluded by a variant """ + self._drill_marks = 'full' + """ What to use to indicate the drill places, can be none, small or full (for real scale) """ + self.color_theme = '_builtin_classic' + """ Selects the color theme. Only applies to KiCad 6. + To use the KiCad 6 default colors select `_builtin_default`. + Usually user colors are stored as `user`, but you can give it another name """ + self.plot_sheet_reference = True + """ Include the title-block """ self.pages = PagesOptions """ [list(dict)] List of pages to include in the output document. Each page contains one or more layers of the PCB """ + self.title = '' + """ Text used to replace the sheet title. %VALUE expansions are allowed. + If it starts with `+` the text is concatenated """ super().__init__() self._expand_ext = 'pdf' self._expand_id = 'assembly' -# @property -# def drill_marks(self): -# return self._drill_marks -# -# @drill_marks.setter -# def drill_marks(self, val): -# if val not in self._drill_marks_map: -# raise KiPlotConfigurationError("Unknown drill mark type: {}".format(val)) -# self._drill_marks = val + @property + def drill_marks(self): + return self._drill_marks + + @drill_marks.setter + def drill_marks(self, val): + if val not in self._drill_marks_map: + raise KiPlotConfigurationError("Unknown drill mark type: {}".format(val)) + self._drill_marks = val def config(self, parent): super().config(parent) if isinstance(self.pages, type): raise KiPlotConfigurationError("Missing `pages` list") - - # self._drill_marks = PCB_PrintOptions._drill_marks_map[self._drill_marks] + self._color_theme = load_color_theme(self.color_theme) + if self._color_theme is None: + raise KiPlotConfigurationError("Unable to load `{}` color theme".format(self.color_theme)) + # Assign a color if none was defined + layer_id2color = self._color_theme.layer_id2color + for p in self.pages: + for la in p.layers: + if not la.color: + if la._id in layer_id2color: + la.color = layer_id2color[la._id] + else: + la.color = "#000000" + self._drill_marks = PCB_PrintOptions._drill_marks_map[self._drill_marks] def filter_components(self): if not self._comps: @@ -264,53 +309,59 @@ class PCB_PrintOptions(VariantOptions): temp_dir = mkdtemp(prefix='tmp-kibot-pcb_print-') logger.debug('- Temporal dir: {}'.format(temp_dir)) # Plot options - plot_controller = PLOT_CONTROLLER(GS.board) - plot_options = plot_controller.GetPlotOptions() - plot_options.SetOutputDirectory(temp_dir) + pc = PLOT_CONTROLLER(GS.board) + po = pc.GetPlotOptions() + po.SetOutputDirectory(temp_dir) # Set General Options: - plot_options.SetPlotValue(True) - plot_options.SetPlotReference(True) - plot_options.SetPlotInvisibleText(False) - plot_options.SetPlotViaOnMaskLayer(False) - plot_options.SetExcludeEdgeLayer(True) - # plot_options.SetPlotPadsOnSilkLayer(False); - plot_options.SetUseAuxOrigin(False) - plot_options.SetNegative(False) - plot_options.SetScale(1.0) - plot_options.SetAutoScale(False) + po.SetExcludeEdgeLayer(True) # We plot it separately + po.SetUseAuxOrigin(False) + po.SetAutoScale(False) + po.SetDrillMarksType(self._drill_marks) # Generate the output pages = [] for n, p in enumerate(self.pages): + self.set_title(p.title if p.title else self.title) # 1) Plot all layers to individual PDF files (B&W) + po.SetPlotFrameRef(False) # We plot it separately + po.SetMirror(p.mirror) + po.SetScale(p.scaling) + po.SetNegative(p.negative_plot) + po.SetPlotViaOnMaskLayer(not p.tent_vias) + if GS.ki5(): + po.SetLineWidth(FromMM(p.line_width)) + po.SetPlotPadsOnSilkLayer(not p.exclude_pads_from_silkscreen) for la in p.layers: id = la._id logger.debug('- Plotting layer {} ({})'.format(la.layer, id)) - plot_options.SetPlotFrameRef(id == p.sheet_reference_layer) - plot_options.SetMirror(p.mirror) - if IsCopperLayer(id): # Should probably do this on mask layers as well - plot_options.SetDrillMarksType(2) # NO_DRILL_SHAPE = 0, SMALL_DRILL_SHAPE = 1, FULL_DRILL_SHAPE = 2 - else: - plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE = 0, SMALL_DRILL_SHAPE = 1, FULL_DRILL_SHAPE = 2 - plot_controller.SetLayer(id) - plot_controller.OpenPlotfile(la.suffix, PLOT_FORMAT_PDF, "Assembly") - plot_controller.PlotLayer() - plot_controller.ClosePlot() - # 2) Apply the colors to the PDFs + po.SetPlotReference(la.plot_footprint_refs) + po.SetPlotValue(la.plot_footprint_values) + po.SetPlotInvisibleText(la.force_plot_invisible_refs_vals) + pc.SetLayer(id) + pc.OpenPlotfile(la.suffix, PLOT_FORMAT_PDF, p.sheet) + pc.PlotLayer() + # 2) Plot the frame using an empry layer and 1.0 scale + if self.plot_sheet_reference: + logger.debug('- Plotting the frame') + po.SetPlotFrameRef(True) + po.SetScale(1.0) + pc.SetLayer(GS.board.GetLayerID(GS.work_layer)) + pc.OpenPlotfile('frame', PLOT_FORMAT_PDF, p.sheet) + pc.PlotLayer() + pc.ClosePlot() + # 3) Apply the colors to the layer PDFs filelist = [] for la in p.layers: - in_file = GS.pcb_basename+"-"+la.suffix+".pdf" - if la.color != "#000000": - out_file = GS.pcb_basename+"-"+la.suffix+"-colored.pdf" - logger.debug('- Giving color to {} -> {} ({})'.format(in_file, out_file, la.color)) - colorize_pdf(temp_dir, in_file, out_file, hex_to_rgb(la.color)) - filelist.append(out_file) - else: - filelist.append(in_file) - # 3) Stack all layers in one file + colorize_layer(la.suffix, la.color, p.monochrome, filelist, temp_dir) + # 4) Apply color to the frame + if self.plot_sheet_reference: + color = p.sheet_reference_color if p.sheet_reference_color else self._color_theme.pcb_frame + colorize_layer('frame', color, p.monochrome, filelist, temp_dir) + # 5) Stack all layers in one file assembly_file = GS.pcb_basename+"-"+str(n)+".pdf" logger.debug('- Merging layers to {}'.format(assembly_file)) merge_pdf(temp_dir, filelist, temp_dir, assembly_file) pages.append(assembly_file) + self.restore_title() # Join all pages in one file logger.debug('- Creating output file {}'.format(output)) create_pdf_from_pages(temp_dir, pages, output) @@ -319,7 +370,6 @@ class PCB_PrintOptions(VariantOptions): def run(self, output): super().run(output) - # self.set_title(self.title) self.filter_components() self.generate_output(output) self.unfilter_components() diff --git a/tests/yaml_samples/pcb_print.kibot.yaml b/tests/yaml_samples/pcb_print.kibot.yaml index 07db5a32..9d293408 100644 --- a/tests/yaml_samples/pcb_print.kibot.yaml +++ b/tests/yaml_samples/pcb_print.kibot.yaml @@ -8,24 +8,27 @@ outputs: type: pcb_print dir: Layers options: -# title: 'Fake title for front copper and silk' + # title: 'Fake title for front copper and silk' + # color_theme: _builtin_default + # drill_marks: small + title: Chau + # plot_sheet_reference: false pages: - - sheet_reference_layer: F.Fab + - # monochrome: true + scaling: 2.0 + title: Hola + sheet: Front + sheet_reference_color: "#A02020" layers: - layer: Edge.Cuts - color: "#FF8000" - layer: F.Cu - color: "#B3FFB3" - layer: F.Paste - color: "#FF8A8A" - layer: F.SilkS - color: "#626262" - layer: F.Fab - color: "#000080" + plot_footprint_refs: false + plot_footprint_values: false - layer: User.Eco1 - color: "#000080" - mirror: true - sheet_reference_layer: B.Fab layers: - layer: B.Fab color: "#000080"