diff --git a/CHANGELOG.md b/CHANGELOG.md index 50499020..7709de8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `field_tolerance` field/s to look for resistor tolerance. - `default_resistor_tolerance` which tolerance to use when none found. - `cache_3d_resistors` to avoid generating them all the time. + - `resources_dir` to specify fonts and colors to install (CI/CD) - 3D: colored 3D models for THT resistors - Datasheet download: now the warnings mention which reference failed. - Plot related outputs and PCB_Print: diff --git a/Makefile b/Makefile index f1054a23..abbd9e97 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,8 @@ test_docker_local_1_ki7: docker run --rm -v $(CWD):$(CWD) --workdir="$(CWD)" ghcr.io/inti-cmnb/kicad_auto_test:ki7 \ /bin/bash -c "python3-coverage run src/kibot --help-outputs > /dev/null; pytest-3 --log-cli-level debug -k '$(SINGLE_TEST)' --test_dir=output ; $(PY_COV) html; chown -R $(USER_ID):$(GROUP_ID) output/ tests/board_samples/ tests/.config/kiplot/plugins/__pycache__/ tests/test_plot/fake_pcbnew/__pycache__/ tests/.config/kibot/plugins/__pycache__/ .coverage htmlcov/" +t1k7: test_docker_local_1_ki7 + test_docker_local: rm -rf output rm -f tests/.local diff --git a/README.md b/README.md index 1d4503b1..8785b39d 100644 --- a/README.md +++ b/README.md @@ -832,6 +832,11 @@ global: Currently known are FR1 to FR5. - `remove_adhesive_for_dnp`: [boolean=true] When applying filters and variants remove the adhesive (glue) for components that won't be included. - `remove_solder_paste_for_dnp`: [boolean=true] When applying filters and variants remove the solder paste for components that won't be included. + - `resources_dir`: [string='kibot_resources'] Directory where various resources are stored. Currently we support colors and fonts. + They must be stored in sub-dirs. I.e. kibot_resources/fonts/MyFont.ttf + Note this is mainly useful for CI/CD, so you can store fonts and colors in your repo. + Also note that the fonts are installed using a mechanism known to work on Debian, + which is used by the KiBot docker images, on other OSs *your mileage may vary*. - `restore_project`: [boolean=false] Restore the KiCad project after execution. Note that this option will undo operations like `set_text_variables`. - `set_text_variables_before_output`: [boolean=false] Run the `set_text_variables` preflight before running each output that involves variants. diff --git a/kibot/globals.py b/kibot/globals.py index e8ca1931..ead90def 100644 --- a/kibot/globals.py +++ b/kibot/globals.py @@ -277,6 +277,12 @@ class Globals(FiltersOptions): self.cache_3d_resistors = False """ Use a cache for the generated 3D models of colored resistors. Will save time, but you could need to remove the cache if you need to regenerate them """ + self.resources_dir = 'kibot_resources' + """ Directory where various resources are stored. Currently we support colors and fonts. + They must be stored in sub-dirs. I.e. kibot_resources/fonts/MyFont.ttf + Note this is mainly useful for CI/CD, so you can store fonts and colors in your repo. + Also note that the fonts are installed using a mechanism known to work on Debian, + which is used by the KiBot docker images, on other OSs *your mileage may vary* """ self.set_doc('filters', " [list(dict)] KiBot warnings to be ignored ") self._filter_what = 'KiBot warnings' self.filters = FilterOptionsKiBot diff --git a/kibot/gs.py b/kibot/gs.py index 4da3124c..2f6a68e4 100644 --- a/kibot/gs.py +++ b/kibot/gs.py @@ -154,6 +154,7 @@ class GS(object): global_pcb_material = None global_remove_solder_paste_for_dnp = None global_remove_adhesive_for_dnp = None + global_resources_dir = None global_restore_project = None global_set_text_variables_before_output = None global_silk_screen_color = None diff --git a/kibot/kicad/color_theme.py b/kibot/kicad/color_theme.py index 3fdc3699..471c305e 100644 --- a/kibot/kicad/color_theme.py +++ b/kibot/kicad/color_theme.py @@ -66,7 +66,8 @@ def load_color_theme(name): fn = os.path.join(GS.get_resource_path('kicad_colors'), name+'.json') else: KiConf.init(GS.pcb_file) - fn = os.path.join(KiConf.config_dir, 'colors', name+'.json') + cfg_dir = KiConf.config_dir or GS.kicad_conf_path or '' + fn = os.path.join(cfg_dir, 'colors', name+'.json') fn = os.path.abspath(fn) global CACHE if fn in CACHE: diff --git a/kibot/kiplot.py b/kibot/kiplot.py index cb202ceb..16313f54 100644 --- a/kibot/kiplot.py +++ b/kibot/kiplot.py @@ -14,7 +14,7 @@ import re from sys import exit from sys import path as sys_path import shlex -from shutil import which +from shutil import which, copy2 from subprocess import run, PIPE, STDOUT, Popen, CalledProcessError from glob import glob from importlib.util import spec_from_file_location, module_from_spec @@ -513,6 +513,7 @@ def _generate_outputs(outputs, targets, invert, skip_pre, cli_order, no_priority def generate_outputs(outputs, targets, invert, skip_pre, cli_order, no_priority, dont_stop=False): + setup_resources() prj = None if GS.global_restore_project: # Memorize the project content to restore it at exit @@ -976,6 +977,54 @@ def _walk(path, depth): yield from _walk(entry.path, depth) +def setup_fonts(source): + if not os.path.isdir(source): + logger.debug('No font resources dir') + return + dest = os.path.expanduser('~/.fonts/') + installed = False + for f in glob(os.path.join(source, '*.ttf')): + fname = os.path.basename(f) + fdest = os.path.join(dest, fname) + if os.path.isfile(fdest): + logger.debug('Font {} already installed'.format(fname)) + continue + logger.info('Installing font {}'.format(fname)) + if not os.path.isdir(dest): + os.makedirs(dest) + copy2(f, fdest) + installed = True + if installed: + run_command(['fc-cache']) + + +def setup_colors(source): + if not os.path.isdir(source): + logger.debug('No color resources dir') + return + if not GS.kicad_conf_path: + return + dest = os.path.join(GS.kicad_conf_path, 'colors') + for f in glob(os.path.join(source, '*.json')): + fname = os.path.basename(f) + fdest = os.path.join(dest, fname) + if os.path.isfile(fdest): + logger.debug('Color {} already installed'.format(fname)) + continue + logger.info('Installing color {}'.format(fname)) + if not os.path.isdir(dest): + os.makedirs(dest) + copy2(f, fdest) + + +def setup_resources(): + if not GS.global_resources_dir: + logger.debug('No resources dir') + return + setup_fonts(os.path.join(GS.global_resources_dir, 'fonts')) + setup_colors(os.path.join(GS.global_resources_dir, 'colors')) + + def generate_examples(start_dir, dry, types): if not start_dir: start_dir = '.' @@ -987,6 +1036,8 @@ def generate_examples(start_dir, dry, types): glb = GS.class_for_global_opts() glb.set_tree({}) glb.config(None) + # Install the resources + setup_resources() # Look for candidate dirs k_files_regex = re.compile(r'([^/]+)\.(kicad_pcb|kicad_sch|sch)$') candidates = set() diff --git a/tests/board_samples/kicad_7/font_and_colors.kicad_pcb b/tests/board_samples/kicad_7/font_and_colors.kicad_pcb new file mode 100644 index 00000000..44f3aabd --- /dev/null +++ b/tests/board_samples/kicad_7/font_and_colors.kicad_pcb @@ -0,0 +1,89 @@ +(kicad_pcb (version 20221018) (generator pcbnew) + + (general + (thickness 1.6) + ) + + (paper "A4") + (layers + (0 "F.Cu" signal) + (31 "B.Cu" signal) + (32 "B.Adhes" user "B.Adhesive") + (33 "F.Adhes" user "F.Adhesive") + (34 "B.Paste" user) + (35 "F.Paste" user) + (36 "B.SilkS" user "B.Silkscreen") + (37 "F.SilkS" user "F.Silkscreen") + (38 "B.Mask" user) + (39 "F.Mask" user) + (40 "Dwgs.User" user "User.Drawings") + (41 "Cmts.User" user "User.Comments") + (42 "Eco1.User" user "User.Eco1") + (43 "Eco2.User" user "User.Eco2") + (44 "Edge.Cuts" user) + (45 "Margin" user) + (46 "B.CrtYd" user "B.Courtyard") + (47 "F.CrtYd" user "F.Courtyard") + (48 "B.Fab" user) + (49 "F.Fab" user) + (50 "User.1" user) + (51 "User.2" user) + (52 "User.3" user) + (53 "User.4" user) + (54 "User.5" user) + (55 "User.6" user) + (56 "User.7" user) + (57 "User.8" user) + (58 "User.9" user) + ) + + (setup + (pad_to_mask_clearance 0) + (pcbplotparams + (layerselection 0x00010fc_ffffffff) + (plot_on_all_layers_selection 0x0000000_00000000) + (disableapertmacros false) + (usegerberextensions false) + (usegerberattributes true) + (usegerberadvancedattributes true) + (creategerberjobfile true) + (dashed_line_dash_ratio 12.000000) + (dashed_line_gap_ratio 3.000000) + (svgprecision 4) + (plotframeref false) + (viasonmask false) + (mode 1) + (useauxorigin false) + (hpglpennumber 1) + (hpglpenspeed 20) + (hpglpendiameter 15.000000) + (dxfpolygonmode true) + (dxfimperialunits true) + (dxfusepcbnewfont true) + (psnegative false) + (psa4output false) + (plotreference true) + (plotvalue true) + (plotinvisibletext false) + (sketchpadsonfab false) + (subtractmaskfromsilk false) + (outputformat 1) + (mirror false) + (drillshape 1) + (scaleselection 1) + (outputdirectory "") + ) + ) + + (net 0 "") + + (gr_rect (start 100 70) (end 150 121) + (stroke (width 0.2) (type default)) (fill none) (layer "Edge.Cuts") (tstamp cd1c9c8d-3ccc-4c8b-b847-9a6aa1c9d599)) + (gr_text "4" (at 105 110) (layer "F.Cu") (tstamp 1dbcdcd1-a8e9-4623-9731-77c1e65b8868) + (effects (font (size 20 20) (thickness 5) bold) (justify left bottom)) + ) + (gr_text "4" (at 125 110) (layer "F.Cu") (tstamp d892df33-6ec0-4783-b75f-e6ee5f7aacc6) + (effects (font (face "Archivo SemiBold") (size 20 20) (thickness 5) bold) (justify left bottom)) + ) + +) diff --git a/tests/data/kibot_resources_1/colors/test_color.json b/tests/data/kibot_resources_1/colors/test_color.json new file mode 100644 index 00000000..049937b2 --- /dev/null +++ b/tests/data/kibot_resources_1/colors/test_color.json @@ -0,0 +1,218 @@ +{ + "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)", + "conflicts_shadow": "rgba(255, 0, 5, 0.502)", + "copper": { + "b": "rgb(77, 127, 196)", + "f": "rgb(114, 194, 200)", + "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(149, 210, 48)", + "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)", + "locked_shadow": "rgba(255, 38, 226, 0.502)", + "margin": "rgb(255, 38, 226)", + "pad_plated_hole": "rgb(194, 194, 0)", + "pad_through_hole": "rgb(227, 183, 46)", + "page_limits": "rgb(132, 132, 132)", + "plated_hole": "rgb(26, 196, 210)", + "ratsnest": "rgba(0, 248, 255, 0.349)", + "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)", + "page_limits": "rgb(132, 132, 132)", + "worksheet": "rgb(0, 0, 132)" + }, + "meta": { + "name": "test_color", + "version": 5 + }, + "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_exclusion": "rgba(94, 194, 194, 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)", + "hovered": "rgb(0, 0, 255)", + "junction": "rgb(0, 150, 0)", + "label_global": "rgb(132, 0, 0)", + "label_hier": "rgb(114, 86, 0)", + "label_local": "rgb(15, 15, 15)", + "netclass_flag": "rgb(72, 72, 72)", + "no_connect": "rgb(0, 0, 132)", + "note": "rgb(0, 0, 194)", + "note_background": "rgba(0, 0, 0, 0.000)", + "override_item_colors": false, + "page_limits": "rgb(181, 181, 181)", + "pin": "rgb(132, 0, 0)", + "pin_name": "rgb(0, 100, 100)", + "pin_number": "rgb(169, 0, 0)", + "private_note": "rgb(72, 72, 255)", + "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/tests/data/kibot_resources_1/fonts/Archivo-SemiBold.ttf b/tests/data/kibot_resources_1/fonts/Archivo-SemiBold.ttf new file mode 100644 index 00000000..87c9b0da Binary files /dev/null and b/tests/data/kibot_resources_1/fonts/Archivo-SemiBold.ttf differ diff --git a/tests/reference/7_0_0/font_and_colors-assembly_page_01.png b/tests/reference/7_0_0/font_and_colors-assembly_page_01.png new file mode 100644 index 00000000..063f61fa Binary files /dev/null and b/tests/reference/7_0_0/font_and_colors-assembly_page_01.png differ diff --git a/tests/reference/7_0_0/font_and_colors-top.png b/tests/reference/7_0_0/font_and_colors-top.png new file mode 100644 index 00000000..1cd31efc Binary files /dev/null and b/tests/reference/7_0_0/font_and_colors-top.png differ diff --git a/tests/test_plot/test_misc.py b/tests/test_plot/test_misc.py index fa9f42c4..c601799c 100644 --- a/tests/test_plot/test_misc.py +++ b/tests/test_plot/test_misc.py @@ -1626,3 +1626,13 @@ def test_panelize_1(test_dir): ctx.run(extra=[]) ctx.compare_image(prj+'-panel.png') ctx.clean_up(keep_project=True) + + +@pytest.mark.skipif(not context.ki7(), reason="Uses fonts") +def test_font_and_colors_1(test_dir): + prj = 'font_and_colors' + ctx = context.TestContext(test_dir, prj, 'resources_1') + ctx.run() + ctx.compare_image(prj+'-top.png') + ctx.compare_image(prj+'-assembly_page_01.png') + ctx.clean_up() diff --git a/tests/yaml_samples/resources_1.kibot.yaml b/tests/yaml_samples/resources_1.kibot.yaml new file mode 100644 index 00000000..69d655af --- /dev/null +++ b/tests/yaml_samples/resources_1.kibot.yaml @@ -0,0 +1,23 @@ +kiplot: + version: 1 + +globals: + resources_dir: tests/data/kibot_resources_1 + +outputs: + - name: PcbDraw + comment: "PcbDraw font test" + type: pcbdraw + options: + format: png + + - name: 'print_front' + comment: "Font and colors" + type: pcb_print + options: + plot_sheet_reference: false + scaling: 3 + format: PNG + color_theme: test_color + pages: + - layers: [F.Cu, Edge.Cuts]