diff --git a/kibot/out_any_drill.py b/kibot/out_any_drill.py index fa9759c6..1cf6563f 100644 --- a/kibot/out_any_drill.py +++ b/kibot/out_any_drill.py @@ -159,7 +159,7 @@ class AnyDrill(VariantOptions): def run(self, output_dir): super().run(output_dir) - self.filter_pcb_components(GS.board) + self.filter_pcb_components() if self.output: output_dir = os.path.dirname(output_dir) # dialog_gendrill.cpp:357 @@ -187,7 +187,7 @@ class AnyDrill(VariantOptions): drill_report_file = self.expand_filename(output_dir, self.report, 'drill_report', 'txt') logger.debug("Generating drill report: "+drill_report_file) drill_writer.GenDrillReportFile(drill_report_file) - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() def get_targets(self, out_dir): targets = [] diff --git a/kibot/out_any_layer.py b/kibot/out_any_layer.py index 658aff16..db589217 100644 --- a/kibot/out_any_layer.py +++ b/kibot/out_any_layer.py @@ -117,7 +117,7 @@ class AnyLayerOptions(VariantOptions): def run(self, output_dir, layers): super().run(output_dir) # Apply the variants and filters - exclude = self.filter_pcb_components(GS.board) + exclude = self.filter_pcb_components() # fresh plot controller plot_ctrl = PLOT_CONTROLLER(GS.board) # set up plot options for the whole output @@ -185,7 +185,7 @@ class AnyLayerOptions(VariantOptions): f.write(content) # Restore the eliminated layers if exclude: - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() def solve_extension(self, layer): if self._plot_format == PLOT_FORMAT_GERBER and self.use_protel_extensions: diff --git a/kibot/out_any_pcb_print.py b/kibot/out_any_pcb_print.py index 3cec4246..d71a1648 100644 --- a/kibot/out_any_pcb_print.py +++ b/kibot/out_any_pcb_print.py @@ -57,12 +57,12 @@ class Any_PCB_PrintOptions(VariantOptions): def filter_components(self): if not self.will_filter_pcb_components() and self.title == '': return GS.pcb_file, None - self.filter_pcb_components(GS.board) + self.filter_pcb_components() self.set_title(self.title) # Save the PCB to a temporal dir fname, pcb_dir = self.save_tmp_dir_board('pdf_pcb_print') self.restore_title() - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() return fname, pcb_dir def get_targets(self, out_dir): diff --git a/kibot/out_any_stencil.py b/kibot/out_any_stencil.py index f9e66b6d..7f398d64 100644 --- a/kibot/out_any_stencil.py +++ b/kibot/out_any_stencil.py @@ -105,14 +105,14 @@ class Stencil_Options(VariantOptions): self.ensure_tool('Xvfb') super().run(output) # Apply variants and filters - filtered = self.filter_pcb_components(GS.board) + filtered = self.filter_pcb_components() if self.side == 'auto': detected_top, detected_bottom = self.detect_solder_paste(GS.board) else: detected_top = detected_bottom = False fname = self.save_tmp_board() if filtered else GS.pcb_file if filtered: - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() # Avoid running the tool if we will generate useless models if self.side == 'auto' and not detected_top and not detected_bottom: logger.warning(W_AUTONONE+'No solder paste detected, skipping stencil generation') diff --git a/kibot/out_base.py b/kibot/out_base.py index 382d80a9..f93ea9bb 100644 --- a/kibot/out_base.py +++ b/kibot/out_base.py @@ -8,9 +8,9 @@ from glob import glob import math import os import re -from tempfile import NamedTemporaryFile, mkdtemp, TemporaryDirectory +from tempfile import NamedTemporaryFile, mkdtemp from .gs import GS -from .kiplot import load_sch, get_board_comps_data, load_board +from .kiplot import load_sch, get_board_comps_data from .misc import Rect, W_WRONGPASTE, DISABLE_3D_MODEL_TEXT, W_NOCRTYD if not GS.kicad_version_n: # When running the regression tests we need it @@ -366,6 +366,7 @@ class VariantOptions(BaseOptions): """ Remove from solder paste layers the filtered components. """ if comps_hash is None or not (GS.global_remove_solder_paste_for_dnp or GS.global_remove_adhesive_for_dnp): return + logger.debug('Removing paste and glue') exclude = LSET() fpaste = board.GetLayerID('F.Paste') bpaste = board.GetLayerID('B.Paste') @@ -398,16 +399,22 @@ class VariantOptions(BaseOptions): logger.warning(W_WRONGPASTE+'Pad with solder paste, but no copper or solder mask aperture in '+ref) p.SetLayerSet(pad_layers) old_layers.append(old_c_layers) + logger.debugl(3, '- Removed paste from '+ref) # Remove any graphical item in the *.Adhes layers if GS.global_remove_adhesive_for_dnp: + found = False for gi in m.GraphicalItems(): l_gi = gi.GetLayer() if l_gi == fadhes: gi.SetLayer(rescue) old_fadhes.append(gi) + found = True if l_gi == badhes: gi.SetLayer(rescue) old_badhes.append(gi) + found = True + if found: + logger.debugl(3, '- Removed adhesive from '+ref) # Store the data to undo the above actions self.old_layers = old_layers self.old_fadhes = old_fadhes @@ -419,11 +426,13 @@ class VariantOptions(BaseOptions): def restore_paste_and_glue(self, board, comps_hash): if comps_hash is None: return + logger.debug('Restoring paste and glue') if GS.global_remove_solder_paste_for_dnp: for m in GS.get_modules_board(board): ref = m.GetReference() c = comps_hash.get(ref, None) if c and c.included and not c.fitted: + logger.debugl(3, '- Restoring paste for '+ref) restore = self.old_layers.pop(0) for p in m.Pads(): pad_layers = p.GetLayerSet() @@ -714,91 +723,54 @@ class VariantOptions(BaseOptions): m.Models().pop() self._highlighted_3D_components = None - def apply_sub_pcb(self): - with TemporaryDirectory(prefix='kibot-separate') as d: - dest = os.path.join(d, os.path.basename(GS.pcb_file)) - # Save the current PCB, with any changes applied - with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False) as f: - pcb_file = f.name - GS.board.Save(pcb_file) - if self._comps: - # Memorize the used modules - old_modules = {m.GetReference() for m in GS.get_modules()} - # Now do the separation - self._sub_pcb.load_board(pcb_file, dest) - # Remove the temporal PCB - os.remove(pcb_file) - # Compute the modules we removed - self._excl_by_sub_pcb = set() - # Now reflect it in the list of components - if self._comps: - logger.debug('Removing components outside the sub-PCB') - # Memorize the used modules - new_modules = {m.GetReference() for m in GS.get_modules()} - diff = old_modules - new_modules - logger.debugl(3, diff) - # Exclude them from _comps - for c in diff: - cmp = self.comps_hash[c] - if cmp.included: - cmp.included = False - self._excl_by_sub_pcb.add(c) - logger.debugl(2, '- Removing '+c) - def will_filter_pcb_components(self): """ True if we will apply filters/variants """ return self._comps or self._sub_pcb - def filter_pcb_components(self, board, do_3D=False, do_2D=True, highlight=None): + def filter_pcb_components(self, do_3D=False, do_2D=True, highlight=None): if not self.will_filter_pcb_components(): return False + self.comps_hash = self.get_refs_hash() + if self._sub_pcb: + self._sub_pcb.apply(self.comps_hash) if self._comps: - self.comps_hash = self.get_refs_hash() if do_2D: - self.cross_modules(board, self.comps_hash) - self.remove_paste_and_glue(board, self.comps_hash) + self.cross_modules(GS.board, self.comps_hash) + self.remove_paste_and_glue(GS.board, self.comps_hash) if hasattr(self, 'hide_excluded') and self.hide_excluded: - self.remove_fab(board, self.comps_hash) + self.remove_fab(GS.board, self.comps_hash) # Copy any change in the schematic fields to the PCB properties # I.e. the value of a component so it gets updated in the *.Fab layer # Also useful for iBoM that can read the sch fields from the PCB - self.sch_fields_to_pcb(board, self.comps_hash) + self.sch_fields_to_pcb(GS.board, self.comps_hash) if do_3D: # Disable the models that aren't for this variant - self.apply_3D_variant_aspect(board) + self.apply_3D_variant_aspect(GS.board) # Remove the 3D models for not fitted components (also rename) - self.remove_3D_models(board, self.comps_hash) + self.remove_3D_models(GS.board, self.comps_hash) # Highlight selected components - self.highlight_3D_models(board, highlight) - if self._sub_pcb: - # Current implementation isn't efficient - self.apply_sub_pcb() + self.highlight_3D_models(GS.board, highlight) return True - def unfilter_pcb_components(self, board, do_3D=False, do_2D=True): + def unfilter_pcb_components(self, do_3D=False, do_2D=True): if not self.will_filter_pcb_components(): return - if self._sub_pcb: - # Undo the sub-PCB: just reload the PCB - GS.board = None - load_board() - for c in self._excl_by_sub_pcb: - self.comps_hash[c].included = True - return - if do_2D: - self.uncross_modules(board, self.comps_hash) - self.restore_paste_and_glue(board, self.comps_hash) + if do_2D and self.comps_hash: + self.uncross_modules(GS.board, self.comps_hash) + self.restore_paste_and_glue(GS.board, self.comps_hash) if hasattr(self, 'hide_excluded') and self.hide_excluded: - self.restore_fab(board, self.comps_hash) + self.restore_fab(GS.board, self.comps_hash) # Restore the PCB properties and values - self.restore_sch_fields_to_pcb(board) - if do_3D: + self.restore_sch_fields_to_pcb(GS.board) + if do_3D and self.comps_hash: # Undo the removing (also rename) - self.restore_3D_models(board, self.comps_hash) + self.restore_3D_models(GS.board, self.comps_hash) # Re-enable the modules that aren't for this variant - self.apply_3D_variant_aspect(board, enable=True) + self.apply_3D_variant_aspect(GS.board, enable=True) # Remove the highlight 3D object - self.unhighlight_3D_models(board) + self.unhighlight_3D_models(GS.board) + if self._sub_pcb: + self._sub_pcb.revert(self.comps_hash) def remove_highlight_3D_file(self): # Remove the highlight 3D file if it was created @@ -894,11 +866,11 @@ class VariantOptions(BaseOptions): if not self.will_filter_pcb_components() and not new_title: return GS.pcb_file logger.debug('Creating modified PCB') - self.filter_pcb_components(GS.board, do_3D=do_3D) + self.filter_pcb_components(do_3D=do_3D) self.set_title(new_title) fname = self.save_tmp_board() self.restore_title() - self.unfilter_pcb_components(GS.board, do_3D=do_3D) + self.unfilter_pcb_components(do_3D=do_3D) to_remove.extend(GS.get_pcb_and_pro_names(fname)) logger.debug('- Modified PCB: '+fname) return fname diff --git a/kibot/out_base_3d.py b/kibot/out_base_3d.py index f9a14ba9..9517fe3b 100644 --- a/kibot/out_base_3d.py +++ b/kibot/out_base_3d.py @@ -201,10 +201,10 @@ class Base3DOptions(VariantOptions): self.undo_3d_models_rename(GS.board) return ret return GS.pcb_file - self.filter_pcb_components(GS.board, do_3D=True, do_2D=True, highlight=highlight) + self.filter_pcb_components(do_3D=True, do_2D=True, highlight=highlight) self.download_models() fname = self.save_tmp_board() - self.unfilter_pcb_components(GS.board, do_3D=True, do_2D=True) + self.unfilter_pcb_components(do_3D=True, do_2D=True) return fname def get_targets(self, out_dir): diff --git a/kibot/out_boardview.py b/kibot/out_boardview.py index e48fe00d..1c0045ca 100644 --- a/kibot/out_boardview.py +++ b/kibot/out_boardview.py @@ -162,10 +162,10 @@ class BoardViewOptions(VariantOptions): def run(self, output): super().run(output) - self.filter_pcb_components(GS.board) + self.filter_pcb_components() with open(output, 'wt') as f: convert(GS.board, f) - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() def get_targets(self, out_dir): return [self._parent.expand_filename(out_dir, self.output)] diff --git a/kibot/out_copy_files.py b/kibot/out_copy_files.py index c8ba4c48..32c25522 100644 --- a/kibot/out_copy_files.py +++ b/kibot/out_copy_files.py @@ -126,7 +126,7 @@ class Copy_FilesOptions(Base3DOptions): dest_dir = dest_dir[:-1] f.output_dir = dest_dir # Apply any variant - self.filter_pcb_components(GS.board, do_3D=True, do_2D=True) + self.filter_pcb_components(do_3D=True, do_2D=True) # Download missing models and rename all collect 3D models (renamed) f.rel_dirs = self.rel_dirs files_list = self.download_models(rename_filter=f.source, rename_function=FilesList.apply_rename, rename_data=f) @@ -139,7 +139,7 @@ class Copy_FilesOptions(Base3DOptions): # We must undo the download/rename self.undo_3d_models_rename(GS.board) else: - self.unfilter_pcb_components(GS.board, do_3D=True, do_2D=True) + self.unfilter_pcb_components(do_3D=True, do_2D=True) # Also include the step/wrl counterpart new_list = [] for fn in files_list: diff --git a/kibot/out_ibom.py b/kibot/out_ibom.py index 55a31c57..882d108f 100644 --- a/kibot/out_ibom.py +++ b/kibot/out_ibom.py @@ -184,9 +184,9 @@ class IBoMOptions(VariantOptions): with open(netlist, 'wb') as f: GS.sch.save_netlist(f, self._comps) # Write a board with the filtered values applied - self.filter_pcb_components(GS.board) + self.filter_pcb_components() pcb_name, _ = self.save_tmp_dir_board('ibom', force_dir=net_dir) - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() else: # Check if the user wants extra_fields but there is no data about them (#68) if self.need_extra_fields() and not os.path.isfile(self.extra_data_file): diff --git a/kibot/out_pcb_print.py b/kibot/out_pcb_print.py index cb7ea73d..6c63f079 100644 --- a/kibot/out_pcb_print.py +++ b/kibot/out_pcb_print.py @@ -1142,9 +1142,9 @@ class PCB_PrintOptions(VariantOptions): svgutils = importlib.import_module('.svgutils.transform', package=__package__) global kicad_worksheet kicad_worksheet = importlib.import_module('.kicad.worksheet', package=__package__) - self.filter_pcb_components(GS.board) + self.filter_pcb_components() self.generate_output(output) - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() @output_class diff --git a/kibot/out_pcb_variant.py b/kibot/out_pcb_variant.py index 4297d3ca..e78552e2 100644 --- a/kibot/out_pcb_variant.py +++ b/kibot/out_pcb_variant.py @@ -33,14 +33,14 @@ class PCB_Variant_Options(VariantOptions): def run(self, output): super().run(output) - self.filter_pcb_components(GS.board, do_3D=True) + self.filter_pcb_components(do_3D=True) self.set_title(self.title) logger.debug('Saving PCB to '+output) GS.board.Save(output) if self.copy_project: GS.copy_project(output) self.restore_title() - self.unfilter_pcb_components(GS.board, do_3D=True) + self.unfilter_pcb_components(do_3D=True) @output_class diff --git a/kibot/out_pcbdraw.py b/kibot/out_pcbdraw.py index 5d1d7f08..7d08844d 100644 --- a/kibot/out_pcbdraw.py +++ b/kibot/out_pcbdraw.py @@ -521,11 +521,11 @@ class PcbDrawOptions(VariantOptions): def run(self, name): super().run(name) # Apply any variant - self.filter_pcb_components(GS.board, do_3D=True) + self.filter_pcb_components(do_3D=True) # Create the image self.create_image(name, GS.board) # Undo the variant - self.unfilter_pcb_components(GS.board, do_3D=True) + self.unfilter_pcb_components(do_3D=True) @output_class diff --git a/kibot/out_position.py b/kibot/out_position.py index 85ef8946..6f03c16b 100644 --- a/kibot/out_position.py +++ b/kibot/out_position.py @@ -222,7 +222,7 @@ class PositionOptions(VariantOptions): def run(self, fname): super().run(fname) - self.filter_pcb_components(GS.board) + self.filter_pcb_components() output_dir = os.path.dirname(fname) columns = self.columns.values() conv = GS.unit_name_to_scale_factor(self.units) @@ -304,7 +304,7 @@ class PositionOptions(VariantOptions): self._do_position_plot_ascii(output_dir, columns, modules, maxlengths, modules_side) else: # if self.format == 'CSV': self._do_position_plot_csv(output_dir, columns, modules, modules_side) - self.unfilter_pcb_components(GS.board) + self.unfilter_pcb_components() @output_class diff --git a/kibot/var_base.py b/kibot/var_base.py index ed04802b..b640bcdb 100644 --- a/kibot/var_base.py +++ b/kibot/var_base.py @@ -3,6 +3,8 @@ # Copyright (c) 2020-2022 Instituto Nacional de TecnologĂ­a Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) +import os +from tempfile import NamedTemporaryFile, TemporaryDirectory from .registrable import RegVariant from .optionable import Optionable, PanelOptions from .fil_base import apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, apply_pre_transform @@ -11,6 +13,9 @@ from .misc import KIKIT_UNIT_ALIASES from .gs import GS from .kiplot import load_board, run_command from .macros import macros, document # noqa: F401 +from . import log + +logger = log.get_logger() class SubPCBOptions(PanelOptions): @@ -66,15 +71,59 @@ class SubPCBOptions(PanelOptions): return "annotation; ref: {}".format(self.reference) return "rectangle; tlx: {}; tly: {}; brx: {}; bry: {}".format(self.tlx, self.tly, self.brx, self.bry) - def load_board(self, pcb_file, dest): + def load_board(self, comps_hash): + """ Apply the sub-PCB using an external tool and load it into memory """ # Make sure kikit is available command = GS.ensure_tool('global', 'KiKit') - # Execute the separate - cmd = [command, 'separate', '-s', self.get_separate_source(), pcb_file, dest] - run_command(cmd) - # Load this board + with TemporaryDirectory(prefix='kibot-separate') as d: + dest = os.path.join(d, os.path.basename(GS.pcb_file)) + # Save the current PCB, with any changes applied + with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False) as f: + pcb_file = f.name + GS.board.Save(pcb_file) + if comps_hash: + # Memorize the used modules + old_modules = {m.GetReference() for m in GS.get_modules()} + # Now do the separation + cmd = [command, 'separate', '-s', self.get_separate_source(), pcb_file, dest] + # Execute the separate + run_command(cmd) + # Load this board + GS.board = None + load_board(dest) + # Remove the temporal PCB + os.remove(pcb_file) + self._excl_by_sub_pcb = set() + # Now reflect the changes in the list of components + if comps_hash: + logger.debug('Removing components outside the sub-PCB') + # Memorize the used modules + new_modules = {m.GetReference() for m in GS.get_modules()} + # Compute the modules we removed + diff = old_modules - new_modules + logger.debugl(3, diff) + # Exclude them from _comps + for c in diff: + cmp = comps_hash[c] + if cmp.included: + cmp.included = False + self._excl_by_sub_pcb.add(c) + logger.debugl(2, '- Removing '+c) + + def apply(self, comps_hash): + if True: + self.load_board(comps_hash) + + def unload_board(self, comps_hash): + # Undo the sub-PCB: just reload the PCB GS.board = None - load_board(dest) + load_board() + for c in self._excl_by_sub_pcb: + comps_hash[c].included = True + + def revert(self, comps_hash): + if True: + self.unload_board(comps_hash) class BaseVariant(RegVariant): diff --git a/tests/yaml_samples/populate_with_filter_sub_pcb_bp.kibot.yaml b/tests/yaml_samples/populate_with_filter_sub_pcb_bp.kibot.yaml index 2dc2cbd2..044a9f77 100644 --- a/tests/yaml_samples/populate_with_filter_sub_pcb_bp.kibot.yaml +++ b/tests/yaml_samples/populate_with_filter_sub_pcb_bp.kibot.yaml @@ -71,4 +71,5 @@ outputs: dir: PopulateWithFilter options: renderer: KiCad_3D + # renderer: PcbDraw input: tests/data/with_filter_html.md