diff --git a/CHANGELOG.md b/CHANGELOG.md index 545e6a50..ba00b85b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Pads removed from *.Paste - Drawings removed from *.Adhes - Components crossed in *.Fab +- STEP (3D) support for variants. ### Fixed - Virtual components are always excluded from position files. diff --git a/README.md b/README.md index 08deb827..7efee06a 100644 --- a/README.md +++ b/README.md @@ -849,7 +849,7 @@ Next time you need this list just use an alias, like this: - `output`: [string='%f-%i%v.%x'] output file name (%i='top_pos'|'bottom_pos'|'both_pos', %x='pos'|'csv'). Affected by global options. - `separate_files_for_front_and_back`: [boolean=true] generate two separated files, one for the top and another for the bottom. - `units`: [string='millimeters'] [millimeters,inches] units used for the positions. - - `variant`: [string=''] Board variant(s) to apply. + - `variant`: [string=''] Board variant to apply. * PS (Postscript) * Type: `ps` @@ -916,6 +916,8 @@ 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 `step` output. * Valid keys: + - `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill. - `metric_units`: [boolean=true] use metric units instead of inches. - `min_distance`: [number=-1] the minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm). - `no_virtual`: [boolean=false] used to exclude 3D models for components with 'virtual' attribute. @@ -923,6 +925,7 @@ Next time you need this list just use an alias, like this: The drill option uses the auxiliar reference defined by the user. You can define any other origin using the format 'X,Y', i.e. '3.2,-10'. - `output`: [string='%f-%i%v.%x'] name for the generated STEP file (%i='3D' %x='step'). Affected by global options. + - `variant`: [string=''] Board variant to apply. * SVG (Scalable Vector Graphics) * Type: `svg` diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index 74afaf3d..e6a8d6a8 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -667,7 +667,7 @@ outputs: separate_files_for_front_and_back: true # [string='millimeters'] [millimeters,inches] units used for the positions units: 'millimeters' - # [string=''] Board variant(s) to apply + # [string=''] Board variant to apply variant: '' # PS (Postscript): @@ -743,6 +743,9 @@ outputs: type: 'step' dir: 'Example/step_dir' options: + # [string|list(string)=''] 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: '' # [boolean=true] use metric units instead of inches metric_units: true # [number=-1] the minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm) @@ -755,6 +758,8 @@ outputs: origin: 'grid' # [string='%f-%i%v.%x'] name for the generated STEP file (%i='3D' %x='step'). Affected by global options output: '%f-%i%v.%x' + # [string=''] Board variant to apply + variant: '' # SVG (Scalable Vector Graphics): # Unlike bitmaps SVG drawings can be scaled without losing resolution. diff --git a/kibot/out_position.py b/kibot/out_position.py index b2b4e069..e6b033b8 100644 --- a/kibot/out_position.py +++ b/kibot/out_position.py @@ -34,7 +34,7 @@ class PositionOptions(BaseOptions): self.units = 'millimeters' """ [millimeters,inches] units used for the positions """ self.variant = '' - """ Board variant(s) to apply """ + """ Board variant to apply """ self.dnf_filter = Optionable """ [string|list(string)=''] Name of the filter to mark components as not fitted. A short-cut to use for simple cases where a variant is an overkill """ diff --git a/kibot/out_step.py b/kibot/out_step.py index 7ede8753..208c6038 100644 --- a/kibot/out_step.py +++ b/kibot/out_step.py @@ -4,11 +4,16 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) import re +import os from subprocess import (check_output, STDOUT, CalledProcessError) +from tempfile import NamedTemporaryFile from .error import KiPlotConfigurationError from .misc import (KICAD2STEP, KICAD2STEP_ERR) from .gs import (GS) -from .optionable import BaseOptions +from .optionable import BaseOptions, Optionable +from .registrable import RegOutput +from .kiplot import load_sch +from .fil_base import BaseFilter, apply_fitted_filter from .macros import macros, document, output_class # noqa: F401 from . import log @@ -30,8 +35,18 @@ class STEPOptions(BaseOptions): """ the minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm) """ self.output = GS.def_global_output """ name for the generated STEP file (%i='3D' %x='step') """ + self.variant = '' + """ Board variant to apply """ + self.dnf_filter = Optionable + """ [string|list(string)=''] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill """ super().__init__() + def config(self): + super().config() + self.variant = RegOutput.check_variant(self.variant) + self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter') + @property def origin(self): return self._origin @@ -42,6 +57,46 @@ class STEPOptions(BaseOptions): raise KiPlotConfigurationError('Origin must be `grid` or `drill` or `X,Y`') self._origin = val + def filter_components(self): + if not self.dnf_filter and not self.variant: + return GS.pcb_file + load_sch() + # Get the components list from the schematic + comps = GS.sch.get_components() + # Apply the filter + apply_fitted_filter(comps, self.dnf_filter) + # Apply the variant + if self.variant: + # Apply the variant + self.variant.filter(comps) + comps_hash = {c.ref: c for c in comps} + # Remove the 3D models for not fitted components + rem_models = [] + for m in GS.board.GetModules(): + ref = m.GetReference() + c = comps_hash.get(ref, None) + if c and not c.fitted: + models = m.Models() + rem_m_models = [] + while not models.empty(): + rem_m_models.append(models.pop()) + rem_models.append(rem_m_models) + # Save the PCB to a temporal file + with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False) as f: + fname = f.name + logger.debug('Storing filtered PCB to `{}`'.format(fname)) + GS.board.Save(fname) + # Undo the removing + for m in GS.board.GetModules(): + ref = m.GetReference() + c = comps_hash.get(ref, None) + if c and not c.fitted: + models = m.Models() + restore = rem_models.pop(0) + for model in restore: + models.push_front(model) + return fname + def run(self, output_dir, board): # Output file name output = self.expand_filename(output_dir, self.output, '3D', 'step') @@ -64,7 +119,8 @@ class STEPOptions(BaseOptions): else: cmd.extend(['--user-origin', "{}{}".format(self.origin.replace(',', 'x'), units)]) # The board - cmd.append(GS.pcb_file) + board_name = self.filter_components() + cmd.append(board_name) # Execute and inform is successful logger.debug('Executing: '+str(cmd)) try: @@ -76,6 +132,10 @@ class STEPOptions(BaseOptions): if e.output: logger.debug('Output from command: '+e.output.decode()) exit(KICAD2STEP_ERR) + finally: + # Remove the temporal PCB + if board_name != GS.pcb_file: + os.remove(board_name) logger.debug('Output from command:\n'+cmd_output.decode()) diff --git a/tests/board_samples/kibom-variant_3.kicad_pcb b/tests/board_samples/kibom-variant_3.kicad_pcb index f626dd3f..9244da56 100644 --- a/tests/board_samples/kibom-variant_3.kicad_pcb +++ b/tests/board_samples/kibom-variant_3.kicad_pcb @@ -128,17 +128,17 @@ (fp_text value 1k (at 0 1.65) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) - (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) (fp_circle (center 0 0) (end 0.4 0) (layer F.Adhes) (width 0.1)) + (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) (fp_text user %R (at 0 0) (layer F.Fab) (effects (font (size 0.5 0.5) (thickness 0.08))) ) @@ -162,17 +162,17 @@ (fp_text value 1000 (at 0 1.65) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) - (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) (fp_circle (center 0 0) (end 0.4 0) (layer F.Adhes) (width 0.1)) + (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) (fp_text user %R (at 0 0) (layer F.Fab) (effects (font (size 0.5 0.5) (thickness 0.08))) ) @@ -199,17 +199,17 @@ (fp_text value 1k (at 0 1.65) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) - (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) (fp_circle (center 0 0) (end 0.4 0) (layer F.Adhes) (width 0.1)) + (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) (fp_text user %R (at 0 0) (layer F.Fab) (effects (font (size 0.5 0.5) (thickness 0.08))) ) @@ -235,17 +235,17 @@ (fp_text value "1000 pF" (at 0 1.65) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) - (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) (fp_circle (center 0 0) (end 0.4 0) (layer F.Adhes) (width 0.1)) + (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) (fp_text user %R (at 0 0) (layer F.Fab) (effects (font (size 0.5 0.5) (thickness 0.08))) ) @@ -272,17 +272,17 @@ (fp_text value 1nF (at 0 1.65) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) - (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) - (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) - (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) - (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) (fp_circle (center 0 0) (end 0.4 0) (layer F.Adhes) (width 0.1)) + (fp_line (start 1.68 0.95) (end -1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start 1.68 -0.95) (end 1.68 0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 -0.95) (end 1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -1.68 0.95) (end -1.68 -0.95) (layer F.CrtYd) (width 0.05)) + (fp_line (start -0.258578 0.71) (end 0.258578 0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start -0.258578 -0.71) (end 0.258578 -0.71) (layer F.SilkS) (width 0.12)) + (fp_line (start 1 0.6) (end -1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start 1 -0.6) (end 1 0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 -0.6) (end 1 -0.6) (layer F.Fab) (width 0.1)) + (fp_line (start -1 0.6) (end -1 -0.6) (layer F.Fab) (width 0.1)) (fp_text user %R (at 0 0) (layer F.Fab) (effects (font (size 0.5 0.5) (thickness 0.08))) ) @@ -295,6 +295,11 @@ (scale (xyz 1 1 1)) (rotate (xyz 0 0 0)) ) + (model ${KISYS3DMOD}/Resistor_SMD.3dshapes/R_0805_2012Metric.wrl + (at (xyz 0 0 0)) + (scale (xyz 1 1 1)) + (rotate (xyz 0 0 0)) + ) ) (gr_text "Bogus component.\nNot in schematic." (at 161.163 89.281) (layer Cmts.User) diff --git a/tests/test_plot/test_step.py b/tests/test_plot/test_step.py index 9c192a36..4f62ee1e 100644 --- a/tests/test_plot/test_step.py +++ b/tests/test_plot/test_step.py @@ -47,3 +47,12 @@ def test_step_3(): # Check all outputs are there ctx.expect_out_file(os.path.join(STEP_DIR, prj+'.step')) ctx.clean_up() + + +def test_step_variant_1(): + prj = 'kibom-variant_3' + ctx = context.TestContext('test_step_variant_1', prj, 'step_variant_1', '') + ctx.run() + # Check all outputs are there + ctx.expect_out_file(prj+'-3D.step') + ctx.clean_up() diff --git a/tests/yaml_samples/step_variant_1.kibot.yaml b/tests/yaml_samples/step_variant_1.kibot.yaml new file mode 100644 index 00000000..9c9a0da4 --- /dev/null +++ b/tests/yaml_samples/step_variant_1.kibot.yaml @@ -0,0 +1,17 @@ +# Example KiBot config file +kibot: + version: 1 + +variants: + - name: 'default' + comment: 'Default variant' + type: ibom + variants_blacklist: T2,T3 + +outputs: + - name: 'step_default' + comment: "STEP w/variant" + type: step + options: + variant: default +