diff --git a/Makefile b/Makefile index 9c2c4ddf..7e9be88e 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ test_docker_local_1: # Run in the same directory to make the __pycache__ valid # Also change the owner of the files to the current user (we run as root like in GitHub) docker run --rm -v $(CWD):$(CWD) --workdir="$(CWD)" setsoft/kicad_auto_test:latest \ - /bin/bash -c "flake8 . --count --statistics ; python3-coverage run -a src/kibot --help-outputs > /dev/null; pytest-3 --log-cli-level debug -k 'test_erc_warning_2' --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/" + /bin/bash -c "flake8 . --count --statistics ; python3-coverage run -a src/kibot --help-outputs > /dev/null; pytest-3 --log-cli-level debug -k 'test_step_2' --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/" #$(PY_COV) report #x-www-browser htmlcov/index.html rm .coverage diff --git a/kibot/kicad/config.py b/kibot/kicad/config.py index 5fba02e9..0e5389f3 100644 --- a/kibot/kicad/config.py +++ b/kibot/kicad/config.py @@ -55,11 +55,17 @@ def un_quote(val): return val -def expand_env(val, env): +def expand_env(val, env, extra_env, used_extra=None): """ Expand KiCad environment variables """ + if used_extra is None: + used_extra = [False] + used_extra[0] = False for var in re.findall(r'\$\{(\S+)\}', val): if var in env: val = val.replace('${'+var+'}', env[var]) + elif var in extra_env: + val = val.replace('${'+var+'}', extra_env[var]) + used_extra[0] = True else: logger.error('Unable to expand `{}` in `{}`'.format(var, val)) return val @@ -80,14 +86,14 @@ class LibAlias(object): self.descr = None @staticmethod - def parse(options, cline, env): + def parse(options, cline, env, extra_env): m = LibAlias.libs_re.match(options) if not m: raise KiConfError('Malformed lib entry', SYM_LIB_TABLE, cline, options) lib = LibAlias() lib.name = un_quote(m.group(1)) lib.legacy = m.group(2) == 'Legacy' - lib.uri = os.path.abspath(expand_env(un_quote(m.group(3)), env)) + lib.uri = os.path.abspath(expand_env(un_quote(m.group(3)), env, extra_env)) lib.options = un_quote(m.group(4)) lib.descr = un_quote(m.group(5)) return lib @@ -337,7 +343,7 @@ class KiConf(object): while line and line[0] != ')': m = re.match(r'\s*\(lib\s*(.*)\)', line) if m: - alias = LibAlias.parse(m.group(1), cline, KiConf.kicad_env) + alias = LibAlias.parse(m.group(1), cline, KiConf.kicad_env, {}) if GS.debug_level > 1: logger.debug('- Adding lib alias '+str(alias)) KiConf.lib_aliases[alias.name] = alias @@ -375,5 +381,7 @@ class KiConf(object): # Load the project's table KiConf.load_lib_aliases(os.path.join(KiConf.dirname, SYM_LIB_TABLE)) - def expand_env(name): - return os.path.abspath(expand_env(un_quote(name), KiConf.kicad_env)) + def expand_env(name, used_extra=None): + if used_extra is None: + used_extra = [False] + return os.path.abspath(expand_env(un_quote(name), KiConf.kicad_env, GS.load_pro_variables(), used_extra)) diff --git a/kibot/out_base_3d.py b/kibot/out_base_3d.py index ee2d268e..806a87ed 100644 --- a/kibot/out_base_3d.py +++ b/kibot/out_base_3d.py @@ -106,6 +106,7 @@ class Base3DOptions(VariantOptions): # List of models we already downloaded downloaded = set() self.undo_3d_models = {} + extra_debug = GS.debug_level > 3 # Look for all the footprints for m in GS.get_modules(): ref = m.GetReference() @@ -118,8 +119,13 @@ class Base3DOptions(VariantOptions): for m3d in models_l: if m3d.m_Filename.endswith(DISABLE_TEXT): # Skip models we intentionally disabled using a bogus name + if extra_debug: + logger.debug("- Skipping {} (disabled)".format(m3d.m_Filename)) continue - full_name = KiConf.expand_env(m3d.m_Filename) + used_extra = [False] + full_name = KiConf.expand_env(m3d.m_Filename, used_extra) + if extra_debug: + logger.debug("- Expanded {} -> {}".format(m3d.m_Filename, full_name)) if not os.path.isfile(full_name): # Missing 3D model if full_name not in downloaded: @@ -148,6 +154,14 @@ class Base3DOptions(VariantOptions): if replace: m3d.m_Filename = replace models_replaced = True + else: # File was found + if used_extra[0]: + # The file is there, but we got it expanding a user defined text + # This is completely valid for KiCad, but kicad2step doesn't support it + m3d.m_Filename = full_name + if not models_replaced and extra_debug: + logger.debug('- Modifying models with text vars') + models_replaced = True # Push the models back for model in models_l: models.push_front(model) @@ -182,11 +196,12 @@ class Base3DOptions(VariantOptions): This mechanism uses the MTEXT attributes. """ # The magic text is %variant:slot1,slot2...% field_regex = re.compile(r'\%([^:]+):(.*)\%') - if GS.debug_level > 3: + extra_debug = GS.debug_level > 3 + if extra_debug: logger.debug("{} 3D models that aren't for this variant".format('Enable' if enable else 'Disable')) len_disable = len(DISABLE_TEXT) for m in GS.get_modules(): - if GS.debug_level > 3: + if extra_debug: logger.debug("Processing module " + m.GetReference()) # Look for text objects for gi in m.GraphicalItems(): @@ -207,7 +222,7 @@ class Base3DOptions(VariantOptions): while not models.empty(): m_objs.insert(0, models.pop()) for i, m3d in enumerate(m_objs): - if GS.debug_level > 3: + if extra_debug: logger.debug('- {} {} {}'.format(i+1, i+1 in slots, m3d.m_Filename)) if i+1 not in slots: if enable: diff --git a/tests/board_samples/kicad_6/.gitignore b/tests/board_samples/kicad_6/.gitignore index f5c92e5b..ef7e3624 100644 --- a/tests/board_samples/kicad_6/.gitignore +++ b/tests/board_samples/kicad_6/.gitignore @@ -3,3 +3,4 @@ print_err.pro test_v5/ zone-refill.pro fp-info-cache +*-bak diff --git a/tests/board_samples/kicad_6/bom_fake_models.kicad_pcb b/tests/board_samples/kicad_6/bom_fake_models.kicad_pcb index 79935419..a9387267 100644 --- a/tests/board_samples/kicad_6/bom_fake_models.kicad_pcb +++ b/tests/board_samples/kicad_6/bom_fake_models.kicad_pcb @@ -105,7 +105,7 @@ (net 2 "Net-(C1-Pad1)") (tstamp a1823eb2-fb0d-4ed8-8b96-04184ac3a9d5)) (pad "2" smd roundrect locked (at 0.9375 0) (size 0.975 1.4) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (net 1 "GND") (tstamp 03c52831-5dc5-43c5-a442-8d23643b46fb)) - (model "${KIPRJMOD}/../../data/R_0805_2012Metric.step" + (model "${MYVAR}/R_0805_2012Metric.step" (offset (xyz 0 0 0)) (scale (xyz 1 1 1)) (rotate (xyz 0 0 0)) diff --git a/tests/board_samples/kicad_6/bom_fake_models.kicad_pro b/tests/board_samples/kicad_6/bom_fake_models.kicad_pro new file mode 100644 index 00000000..eecc5d97 --- /dev/null +++ b/tests/board_samples/kicad_6/bom_fake_models.kicad_pro @@ -0,0 +1,220 @@ +{ + "board": { + "design_settings": { + "defaults": { + "board_outline_line_width": 0.049999999999999996, + "copper_line_width": 0.19999999999999998, + "copper_text_italic": false, + "copper_text_size_h": 1.5, + "copper_text_size_v": 1.5, + "copper_text_thickness": 0.3, + "copper_text_upright": false, + "courtyard_line_width": 0.049999999999999996, + "dimension_precision": 4, + "dimension_units": 3, + "dimensions": { + "arrow_length": 1270000, + "extension_offset": 500000, + "keep_text_aligned": true, + "suppress_zeroes": false, + "text_position": 0, + "units_format": 1 + }, + "fab_line_width": 0.09999999999999999, + "fab_text_italic": false, + "fab_text_size_h": 1.0, + "fab_text_size_v": 1.0, + "fab_text_thickness": 0.15, + "fab_text_upright": false, + "other_line_width": 0.09999999999999999, + "other_text_italic": false, + "other_text_size_h": 1.0, + "other_text_size_v": 1.0, + "other_text_thickness": 0.15, + "other_text_upright": false, + "pads": { + "drill": 0.762, + "height": 1.524, + "width": 1.524 + }, + "silk_line_width": 0.12, + "silk_text_italic": false, + "silk_text_size_h": 1.0, + "silk_text_size_v": 1.0, + "silk_text_thickness": 0.15, + "silk_text_upright": false, + "zones": { + "45_degree_only": true, + "min_clearance": 0.508 + } + }, + "diff_pair_dimensions": [ + { + "gap": 0.0, + "via_gap": 0.0, + "width": 0.0 + } + ], + "drc_exclusions": [], + "meta": { + "filename": "board_design_settings.json", + "version": 2 + }, + "rule_severities": { + "annular_width": "error", + "clearance": "error", + "copper_edge_clearance": "error", + "courtyards_overlap": "error", + "diff_pair_gap_out_of_range": "error", + "diff_pair_uncoupled_length_too_long": "error", + "drill_out_of_range": "error", + "duplicate_footprints": "warning", + "extra_footprint": "warning", + "footprint_type_mismatch": "error", + "hole_clearance": "error", + "hole_near_hole": "error", + "invalid_outline": "error", + "item_on_disabled_layer": "error", + "items_not_allowed": "error", + "length_out_of_range": "error", + "malformed_courtyard": "error", + "microvia_drill_out_of_range": "error", + "missing_courtyard": "ignore", + "missing_footprint": "warning", + "net_conflict": "warning", + "npth_inside_courtyard": "ignore", + "padstack": "error", + "pth_inside_courtyard": "ignore", + "shorting_items": "error", + "silk_over_copper": "warning", + "silk_overlap": "warning", + "skew_out_of_range": "error", + "through_hole_pad_without_hole": "error", + "too_many_vias": "error", + "track_dangling": "warning", + "track_width": "error", + "tracks_crossing": "error", + "unconnected_items": "error", + "unresolved_variable": "error", + "via_dangling": "warning", + "zone_has_empty_net": "error", + "zones_intersect": "error" + }, + "rules": { + "allow_blind_buried_vias": false, + "allow_microvias": false, + "max_error": 0.005, + "min_clearance": 0.0, + "min_copper_edge_clearance": 0.024999999999999998, + "min_hole_clearance": 0.25, + "min_hole_to_hole": 0.25, + "min_microvia_diameter": 0.19999999999999998, + "min_microvia_drill": 0.09999999999999999, + "min_silk_clearance": 0.0, + "min_through_hole_diameter": 0.2032, + "min_track_width": 0.127, + "min_via_annular_width": 0.049999999999999996, + "min_via_diameter": 0.4572, + "use_height_for_length_calcs": true + }, + "track_widths": [ + 0.0, + 0.1524, + 0.3048, + 0.635 + ], + "via_dimensions": [ + { + "diameter": 0.0, + "drill": 0.0 + }, + { + "diameter": 0.508, + "drill": 0.254 + }, + { + "diameter": 0.889, + "drill": 0.508 + } + ], + "zones_allow_external_fillets": false, + "zones_use_no_outline": true + }, + "layer_presets": [] + }, + "boards": [], + "cvpcb": { + "equivalence_files": [] + }, + "libraries": { + "pinned_footprint_libs": [], + "pinned_symbol_libs": [] + }, + "meta": { + "filename": "light_control.kicad_pro", + "version": 1 + }, + "net_settings": { + "classes": [ + { + "bus_width": 12.0, + "clearance": 0.1524, + "diff_pair_gap": 0.25, + "diff_pair_via_gap": 0.25, + "diff_pair_width": 0.2, + "line_style": 0, + "microvia_diameter": 0.3, + "microvia_drill": 0.1, + "name": "Default", + "pcb_color": "rgba(0, 0, 0, 0.000)", + "schematic_color": "rgba(0, 0, 0, 0.000)", + "track_width": 0.1524, + "via_diameter": 0.508, + "via_drill": 0.254, + "wire_width": 6.0 + }, + { + "bus_width": 12.0, + "clearance": 0.1524, + "diff_pair_gap": 0.25, + "diff_pair_via_gap": 0.25, + "diff_pair_width": 0.2, + "line_style": 0, + "microvia_diameter": 0.3, + "microvia_drill": 0.1, + "name": "Power", + "nets": [], + "pcb_color": "rgba(0, 0, 0, 0.000)", + "schematic_color": "rgba(0, 0, 0, 0.000)", + "track_width": 0.635, + "via_diameter": 0.889, + "via_drill": 0.508, + "wire_width": 6.0 + } + ], + "meta": { + "version": 2 + }, + "net_colors": null + }, + "pcbnew": { + "last_paths": { + "gencad": "", + "idf": "", + "netlist": "", + "specctra_dsn": "", + "step": "", + "vrml": "" + }, + "page_layout_descr_file": "" + }, + "schematic": { + "legacy_lib_dir": "", + "legacy_lib_list": [] + }, + "sheets": [], + "text_variables": { + "MYVAR": "tests/data", + "PRUEBITA": "Hola!" + } +} \ No newline at end of file diff --git a/tests/test_plot/test_step.py b/tests/test_plot/test_step.py index 1cf7386e..d121c052 100644 --- a/tests/test_plot/test_step.py +++ b/tests/test_plot/test_step.py @@ -39,11 +39,15 @@ def test_step_1(test_dir): def test_step_2(test_dir): prj = 'bom_fake_models' + yaml = 'step_simple_2' + if context.ki6(): + yaml += '_k6' ctx = context.TestContext(test_dir, 'STEP_2', prj, 'step_simple_2', STEP_DIR) ctx.run() # Check all outputs are there ctx.expect_out_file(os.path.join(STEP_DIR, prj+'-3D.step')) - ctx.clean_up() + ctx.search_err(['Missing 3D model for C1', 'Could not add 3D model to C1'], invert=True) + ctx.clean_up(keep_project=True) def test_step_3(test_dir): diff --git a/tests/yaml_samples/step_simple_2_k6.kibot.yaml b/tests/yaml_samples/step_simple_2_k6.kibot.yaml new file mode 100644 index 00000000..378cb7e4 --- /dev/null +++ b/tests/yaml_samples/step_simple_2_k6.kibot.yaml @@ -0,0 +1,19 @@ +kiplot: + version: 1 + +preflight: + set_text_variables: + - name: "MYVAR" + text: "tests/data" + +outputs: + - name: Step + comment: "Generate 3D model (STEP)" + type: step + dir: 3D + options: + metric_units: false + origin: "3.2,-10" # "grid" or "drill" o "X,Y" i.e. 3.2,-10 + no_virtual: true # exclude 3D models for components with 'virtual' attribute + min_distance: 0.0004 # Minimum distance between points to treat them as separate ones (default 0.01 mm) + #output: project.step