From 4368364489e84c5301437358aac31c2cd2d130ff Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Wed, 8 Jul 2020 15:17:24 -0300 Subject: [PATCH] Refactored the BaseOutput class to make it more similar to the YAML Now the options are just an attribute of the outputs. It means we have a set of classes for the options. All the output content is parsed by the classes, including the Layers. Now the layers support a simple string (with interesting shortcuts), a list of strings or the original list of dicts. --- CHANGELOG.md | 2 + Makefile | 1 + README.md | 527 ++++++++++++------ docs/README.in | 82 ++- docs/samples/generic_plot.kiplot.yaml | 434 +-------------- kiplot/__main__.py | 5 +- kiplot/config_reader.py | 164 ++---- kiplot/drill_marks.py | 10 +- kiplot/error.py | 17 + kiplot/gs.py | 1 + kiplot/kiplot.py | 141 +---- kiplot/layer.py | 211 +++++++ kiplot/optionable.py | 57 +- kiplot/out_any_drill.py | 27 +- kiplot/out_any_layer.py | 46 +- kiplot/out_base.py | 64 +-- kiplot/out_dxf.py | 38 +- kiplot/out_excellon.py | 28 +- kiplot/out_gerb_drill.py | 30 +- kiplot/out_gerber.py | 25 +- kiplot/out_hpgl.py | 35 +- kiplot/out_ibom.py | 31 +- kiplot/out_kibom.py | 28 +- kiplot/out_pdf.py | 37 +- kiplot/out_pdf_pcb_print.py | 58 +- kiplot/out_pdf_sch_print.py | 28 +- kiplot/out_position.py | 24 +- kiplot/out_ps.py | 35 +- kiplot/out_step.py | 26 +- kiplot/out_svg.py | 37 +- kiplot/reg_out.py | 25 + tests/board_samples/good-project.kicad_pcb | 2 +- tests/test_plot/test_misc.py | 18 +- tests/test_plot/test_svg.py | 197 +++++++ tests/test_plot/test_yaml_errors.py | 39 +- .../yaml_samples/error_needs_type.kiplot.yaml | 28 + tests/yaml_samples/error_unk_attr.kiplot.yaml | 4 +- .../error_wrong_layer_7.kiplot.yaml | 27 + .../error_wrong_layer_8.kiplot.yaml | 28 + tests/yaml_samples/svg_all.kiplot.yaml | 24 + tests/yaml_samples/svg_anchor.kiplot.yaml | 33 ++ .../svg_copper_and_cmt.kiplot.yaml | 27 + .../svg_copper_and_draw.kiplot.yaml | 29 + .../svg_copper_and_user.kiplot.yaml | 26 + tests/yaml_samples/svg_selected.kiplot.yaml | 24 + 45 files changed, 1631 insertions(+), 1149 deletions(-) create mode 100644 kiplot/layer.py create mode 100644 kiplot/reg_out.py create mode 100644 tests/yaml_samples/error_needs_type.kiplot.yaml create mode 100644 tests/yaml_samples/error_wrong_layer_7.kiplot.yaml create mode 100644 tests/yaml_samples/error_wrong_layer_8.kiplot.yaml create mode 100644 tests/yaml_samples/svg_all.kiplot.yaml create mode 100644 tests/yaml_samples/svg_anchor.kiplot.yaml create mode 100644 tests/yaml_samples/svg_copper_and_cmt.kiplot.yaml create mode 100644 tests/yaml_samples/svg_copper_and_draw.kiplot.yaml create mode 100644 tests/yaml_samples/svg_copper_and_user.kiplot.yaml create mode 100644 tests/yaml_samples/svg_selected.kiplot.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 19c16214..12533311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 them. ### Added +- The layers entry is much more flexible now. + Many changes, read the README.md - -e/--schematic option to specify any schematic (not just derived from the PCB name. - -x/--example option to generate a complete configuration example. diff --git a/Makefile b/Makefile index ca4ce38b..81acb04e 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ test: lint test_local: lint rm -rf output + rm -f example.kiplot.yaml $(PY_COV) erase pytest-3 --test_dir output $(PY_COV) report diff --git a/README.md b/README.md index afca5fe2..51d0d4f9 100644 --- a/README.md +++ b/README.md @@ -194,177 +194,323 @@ outputs: use_gerber_net_attributes: false layers: - - layer: F.Cu - suffix: F_Cu - - layer: B.Cu - suffix: B_Cu + - 'F.Cu' + - 'B.Cu' ``` Most options are the same you'll find in the KiCad dialogs. + +### Specifying the layers + +You have various ways to specify the layers. If you need to specify just one layer you can just use its name: + +``` + layers: 'F.Cu' +``` + +If you want to specify all the available layers: + +``` + layers: 'all' +``` + +You can also select the layers you want in KiCad (using File, Plot dialog) and save your PCB. +Then you just need to use: + +``` + layers: 'selected' +``` + +You can also use any of the following grup of layers: + +- **copper** all the copper layers +- **technical** all the technical layers (silk sreen, solder mask, paste, adhesive, etc.) +- **user** all the user layers (draw, comments, eco, margin, edge cuts, etc.) + +You can also mix the above definitions using a list: + +``` + layers: + - 'copper' + - 'Dwgs.User' +``` + +This will select all the copper layers and the user drawings. +Note that the above mentioned options will use file name suffixes and descriptions selected automatically. +If you want to use a particular suffix and provide better descriptions you can use the following format: + +``` + layers: + - layer: 'F.Cu' + suffix: 'F_Cu' + description: 'Front copper' + - layer: 'B.Cu' + suffix: 'B_Cu' + description: 'Bottom copper' +``` + +You can also mix the styles: + +``` + layers: + - 'copper' + - layer: 'Cmts.User' + suffix: 'Cmts_User' + description: 'User comments' + - 'Dwgs.User' +``` + +If you need to use the same list of layers for various outputs you can use YAML anchors. +The first time you define the list of layers just assign an ancho, here is an example: + +``` + layers: &copper_and_cmts + - copper + - 'Cmts.User' +``` + +Next time you need this list just use an alias, like this: + +``` + layers: *copper_and_cmts +``` + ### Supported outputs: * DXF (Drawing Exchange Format) * Type: `dxf` * Description: Exports the PCB to 2D mechanical EDA tools (like AutoCAD). This output is what you get from the File/Plot menu in pcbnew. - * Options: - - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `metric_units`: [boolean=false] use mm instead of inches. - - `plot_footprint_refs`: [boolean=true] include the footprint references. - - `plot_footprint_values`: [boolean=true] include the footprint values. - - `plot_sheet_reference`: [boolean=false] currently without effect. - - `polygon_mode`: [boolean=true] plot using the contour, instead of the center line. - - `sketch_plot`: [boolean=false] don't fill objects, just draw the outline. - - `tent_vias`: [boolean=true] cover the vias. - - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `dxf` output. + * Valid keys: + - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). + - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. + - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. + - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. + - `metric_units`: [boolean=false] use mm instead of inches. + - `plot_footprint_refs`: [boolean=true] include the footprint references. + - `plot_footprint_values`: [boolean=true] include the footprint values. + - `plot_sheet_reference`: [boolean=false] currently without effect. + - `polygon_mode`: [boolean=true] plot using the contour, instead of the center line. + - `sketch_plot`: [boolean=false] don't fill objects, just draw the outline. + - `tent_vias`: [boolean=true] cover the vias. + - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. * Excellon drill format * Type: `excellon` * Description: This is the main format for the drilling machine. You can create a map file for documentation purposes. This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew. - * Options: - - `map`: [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map. - Not generated unless a format is specified. - * Options: - - `type`: [string='pdf'] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map. - - `metric_units`: [boolean=true] use metric units instead of inches. - - `minimal_header`: [boolean=false] use a minimal header in the file. - - `mirror_y_axis`: [boolean=false] invert the Y axis. - - `pth_and_npth_single_file`: [boolean=true] generate one file for both, plated holes and non-plated holes, instead of two separated files. - - `report`: [dict|string] name of the drill report. Not generated unless a name is specified. - * Options: - - `filename`: [string=''] name of the drill report. Not generated unless a name is specified. - - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `excellon` output. + * Valid keys: + - `map`: [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map. + Not generated unless a format is specified. + * Valid keys: + - No available options + - `metric_units`: [boolean=true] use metric units instead of inches. + - `minimal_header`: [boolean=false] use a minimal header in the file. + - `mirror_y_axis`: [boolean=false] invert the Y axis. + - `pth_and_npth_single_file`: [boolean=true] generate one file for both, plated holes and non-plated holes, instead of two separated files. + - `report`: [dict|string] name of the drill report. Not generated unless a name is specified. + * Valid keys: + - `filename`: [string=''] name of the drill report. Not generated unless a name is specified. + - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. * Gerber drill format * Type: `gerb_drill` * Description: This is the information for the drilling machine in gerber format. You can create a map file for documentation purposes. This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew. - * Options: - - `map`: [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map. - Not generated unless a format is specified. - * Options: - - `type`: [string='pdf'] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map. - - `report`: [dict|string] name of the drill report. Not generated unless a name is specified. - * Options: - - `filename`: [string=''] name of the drill report. Not generated unless a name is specified. - - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `gerb_drill` output. + * Valid keys: + - `map`: [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map. + Not generated unless a format is specified. + * Valid keys: + - No available options + - `report`: [dict|string] name of the drill report. Not generated unless a name is specified. + * Valid keys: + - `filename`: [string=''] name of the drill report. Not generated unless a name is specified. + - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. * Gerber format * Type: `gerber` * Description: This is the main fabrication format for the PCB. This output is what you get from the File/Plot menu in pcbnew. - * Options: - - `create_gerber_job_file`: [boolean=true] creates a file with information about all the generated gerbers. - You can use it in gerbview to load all gerbers at once. - - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `gerber_precision`: [number=4.6] this the gerber coordinate format, can be 4.5 or 4.6. - - `line_width`: [number=0.1] [0.02,2] line_width for objects without width [mm]. - - `plot_footprint_refs`: [boolean=true] include the footprint references. - - `plot_footprint_values`: [boolean=true] include the footprint values. - - `plot_sheet_reference`: [boolean=false] currently without effect. - - `subtract_mask_from_silk`: [boolean=false] substract the solder mask from the silk screen. - - `tent_vias`: [boolean=true] cover the vias. - - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. - - `use_gerber_net_attributes`: [boolean=true] include netlist metadata. - - `use_gerber_x2_attributes`: [boolean=true] use the extended X2 format. - - `use_protel_extensions`: [boolean=false] use legacy Protel file extensions. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `gerber` output. + * Valid keys: + - `create_gerber_job_file`: [boolean=true] creates a file with information about all the generated gerbers. + You can use it in gerbview to load all gerbers at once. + - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. + - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. + - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. + - `gerber_precision`: [number=4.6] this the gerber coordinate format, can be 4.5 or 4.6. + - `line_width`: [number=0.1] [0.02,2] line_width for objects without width [mm]. + - `plot_footprint_refs`: [boolean=true] include the footprint references. + - `plot_footprint_values`: [boolean=true] include the footprint values. + - `plot_sheet_reference`: [boolean=false] currently without effect. + - `subtract_mask_from_silk`: [boolean=false] substract the solder mask from the silk screen. + - `tent_vias`: [boolean=true] cover the vias. + - `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates. + - `use_gerber_net_attributes`: [boolean=true] include netlist metadata. + - `use_gerber_x2_attributes`: [boolean=true] use the extended X2 format. + - `use_protel_extensions`: [boolean=false] use legacy Protel file extensions. * HPGL (Hewlett & Packard Graphics Language) * Type: `hpgl` * Description: Exports the PCB for plotters and laser printers. This output is what you get from the File/Plot menu in pcbnew. - * Options: - - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `mirror_plot`: [boolean=false] plot mirrored. - - `pen_number`: [number=1] [1,16] pen number. - - `pen_speed`: [number=20] [1,99] pen speed. - - `pen_width`: [number=15] [0,100] pen diameter in MILS, useful to fill areas. However, it is in mm in HPGL files. - - `plot_footprint_refs`: [boolean=true] include the footprint references. - - `plot_footprint_values`: [boolean=true] include the footprint values. - - `plot_sheet_reference`: [boolean=false] currently without effect. - - `scaling`: [number=0] scale factor (0 means autoscaling). - - `sketch_plot`: [boolean=false] don't fill objects, just draw the outline. - - `tent_vias`: [boolean=true] cover the vias. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `hpgl` output. + * Valid keys: + - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). + - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. + - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. + - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. + - `mirror_plot`: [boolean=false] plot mirrored. + - `pen_number`: [number=1] [1,16] pen number. + - `pen_speed`: [number=20] [1,99] pen speed. + - `pen_width`: [number=15] [0,100] pen diameter in MILS, useful to fill areas. However, it is in mm in HPGL files. + - `plot_footprint_refs`: [boolean=true] include the footprint references. + - `plot_footprint_values`: [boolean=true] include the footprint values. + - `plot_sheet_reference`: [boolean=false] currently without effect. + - `scaling`: [number=0] scale factor (0 means autoscaling). + - `sketch_plot`: [boolean=false] don't fill objects, just draw the outline. + - `tent_vias`: [boolean=true] cover the vias. * IBoM (Interactive HTML BoM) * Type: `ibom` * Description: Generates an interactive web page useful to identify the position of the components in the PCB. For more information: https://github.com/INTI-CMNB/InteractiveHtmlBom This output is what you get from the InteractiveHtmlBom plug-in (pcbnew). - * Options: - - `blacklist`: [string=''] List of comma separated blacklisted components or prefixes with *. E.g. 'X1,MH*'. - - `blacklist_empty_val`: [boolean=false] Blacklist components with empty value. - - `board_rotation`: [number=0] Board rotation in degrees (-180 to 180). Will be rounded to multiple of 5. - - `bom_view`: [string='left-right'] [bom-only,left-right,top-bottom] Default BOM view. - - `checkboxes`: [string='Sourced,Placed'] Comma separated list of checkbox columns. - - `dark_mode`: [boolean=false] Default to dark mode. - - `dnp_field`: [string=''] Name of the extra field that indicates do not populate status. Components with this field not empty will be - blacklisted. - - `extra_fields`: [string=''] Comma separated list of extra fields to pull from netlist or xml file. - - `hide_pads`: [boolean=false] Hide footprint pads by default. - - `hide_silkscreen`: [boolean=false] Hide silkscreen by default. - - `highlight_pin1`: [boolean=false] Highlight pin1 by default. - - `include_nets`: [boolean=false] Include netlist information in output.. - - `include_tracks`: [boolean=false] Include track/zone information in output. F.Cu and B.Cu layers only. - - `layer_view`: [string='FB'] [F,FB,B] Default layer view. - - `name_format`: [string='ibom'] Output file name format supports substitutions: - %f : original pcb file name without extension. - %p : pcb/project title from pcb metadata. - %c : company from pcb metadata. - %r : revision from pcb metadata. - %d : pcb date from metadata if available, file modification date otherwise. - %D : bom generation date. - %T : bom generation time. Extension .html will be added automatically. - - `netlist_file`: [string=''] Path to netlist or xml file. - - `no_blacklist_virtual`: [boolean=false] Do not blacklist virtual components. - - `no_redraw_on_drag`: [boolean=false] Do not redraw pcb on drag by default. - - `normalize_field_case`: [boolean=false] Normalize extra field name case. E.g. 'MPN' and 'mpn' will be considered the same field. - - `show_fabrication`: [boolean=false] Show fabrication layer by default. - - `sort_order`: [string='C,R,L,D,U,Y,X,F,SW,A,~,HS,CNN,J,P,NT,MH'] Default sort order for components. Must contain '~' once. - - `variant_field`: [string=''] Name of the extra field that stores board variant for component. - - `variants_blacklist`: [string=''] List of board variants to exclude from the BOM. - - `variants_whitelist`: [string=''] List of board variants to include in the BOM. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `ibom` output. + * Valid keys: + - `blacklist`: [string=''] List of comma separated blacklisted components or prefixes with *. E.g. 'X1,MH*'. + - `blacklist_empty_val`: [boolean=false] Blacklist components with empty value. + - `board_rotation`: [number=0] Board rotation in degrees (-180 to 180). Will be rounded to multiple of 5. + - `bom_view`: [string='left-right'] [bom-only,left-right,top-bottom] Default BOM view. + - `checkboxes`: [string='Sourced,Placed'] Comma separated list of checkbox columns. + - `dark_mode`: [boolean=false] Default to dark mode. + - `dnp_field`: [string=''] Name of the extra field that indicates do not populate status. Components with this field not empty will be + blacklisted. + - `extra_fields`: [string=''] Comma separated list of extra fields to pull from netlist or xml file. + - `hide_pads`: [boolean=false] Hide footprint pads by default. + - `hide_silkscreen`: [boolean=false] Hide silkscreen by default. + - `highlight_pin1`: [boolean=false] Highlight pin1 by default. + - `include_nets`: [boolean=false] Include netlist information in output.. + - `include_tracks`: [boolean=false] Include track/zone information in output. F.Cu and B.Cu layers only. + - `layer_view`: [string='FB'] [F,FB,B] Default layer view. + - `name_format`: [string='ibom'] Output file name format supports substitutions: + %f : original pcb file name without extension. + %p : pcb/project title from pcb metadata. + %c : company from pcb metadata. + %r : revision from pcb metadata. + %d : pcb date from metadata if available, file modification date otherwise. + %D : bom generation date. + %T : bom generation time. Extension .html will be added automatically. + - `netlist_file`: [string=''] Path to netlist or xml file. + - `no_blacklist_virtual`: [boolean=false] Do not blacklist virtual components. + - `no_redraw_on_drag`: [boolean=false] Do not redraw pcb on drag by default. + - `normalize_field_case`: [boolean=false] Normalize extra field name case. E.g. 'MPN' and 'mpn' will be considered the same field. + - `show_fabrication`: [boolean=false] Show fabrication layer by default. + - `sort_order`: [string='C,R,L,D,U,Y,X,F,SW,A,~,HS,CNN,J,P,NT,MH'] Default sort order for components. Must contain '~' once. + - `variant_field`: [string=''] Name of the extra field that stores board variant for component. + - `variants_blacklist`: [string=''] List of board variants to exclude from the BOM. + - `variants_whitelist`: [string=''] List of board variants to include in the BOM. * KiBoM (KiCad Bill of Materials) * Type: `kibom` * Description: Used to generate the BoM in HTML or CSV format using the KiBoM plug-in. For more information: https://github.com/INTI-CMNB/KiBoM This output is what you get from the 'Tools/Generate Bill of Materials' menu in eeschema. - * Options: - - `conf`: [string='bom.ini'] BoM configuration file, relative to PCB. - - `format`: [string='HTML'] [HTML,CSV] format for the BoM. - - `number`: [number=1] Number of boards to build (components multiplier). - - `separator`: [string=','] CSV Separator. - - `variant`: [string=''] Board variant(s), used to determine which components - are output to the BoM. To specify multiple variants, - with a BOM file exported for each variant, separate - variants with the ';' (semicolon) character. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `kibom` output. + * Valid keys: + - `conf`: [string='bom.ini'] BoM configuration file, relative to PCB. + - `format`: [string='HTML'] [HTML,CSV] format for the BoM. + - `number`: [number=1] Number of boards to build (components multiplier). + - `separator`: [string=','] CSV Separator. + - `variant`: [string=''] Board variant(s), used to determine which components + are output to the BoM. To specify multiple variants, + with a BOM file exported for each variant, separate + variants with the ';' (semicolon) character. * PDF (Portable Document Format) * Type: `pdf` * Description: Exports the PCB to the most common exhange format. Suitable for printing. Note that this output isn't the best for documating your project. This output is what you get from the File/Plot menu in pcbnew. - * Options: + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `line_width`: [number=0.1] [0.02,2] for objects without width [mm]. - - `mirror_plot`: [boolean=false] plot mirrored. - - `negative_plot`: [boolean=false] invert black and white. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `pdf` output. + * Valid keys: + - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). + - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. + - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. + - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. + - `line_width`: [number=0.1] [0.02,2] for objects without width [mm]. + - `mirror_plot`: [boolean=false] plot mirrored. + - `negative_plot`: [boolean=false] invert black and white. + - `plot_footprint_refs`: [boolean=true] include the footprint references. + - `plot_footprint_values`: [boolean=true] include the footprint values. + - `plot_sheet_reference`: [boolean=false] currently without effect. + - `tent_vias`: [boolean=true] cover the vias. - `plot_footprint_refs`: [boolean=true] include the footprint references. - `plot_footprint_values`: [boolean=true] include the footprint values. - `plot_sheet_reference`: [boolean=false] currently without effect. @@ -375,82 +521,130 @@ Most options are the same you'll find in the KiCad dialogs. * Description: Exports the PCB to the most common exhange format. Suitable for printing. This is the main format to document your PCB. This output is what you get from the 'File/Print' menu in pcbnew. - * Options: - - `output_name`: [string=''] filename for the output PDF (the name of the PCB if empty). + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to include in the PDF. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `pdf_pcb_print` output. + * Valid keys: + - `output_name`: [string=''] filename for the output PDF (the name of the PCB if empty). * PDF Schematic Print (Portable Document Format) * Type: `pdf_sch_print` * Description: Exports the PCB to the most common exhange format. Suitable for printing. This is the main format to document your schematic. This output is what you get from the 'File/Print' menu in eeschema. - * Options: - - `output`: [string=''] filename for the output PDF (the name of the schematic if empty). + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `pdf_sch_print` output. + * Valid keys: + - `output`: [string=''] filename for the output PDF (the name of the schematic if empty). * Pick & place * Type: `position` * Description: Generates the file with position information for the PCB components, used by the pick and place machine. This output is what you get from the 'File/Fabrication output/Footprint poistion (.pos) file' menu in pcbnew. - * Options: - - `format`: [string='ASCII'] [ASCII,CSV] format for the position file. - - `only_smd`: [boolean=true] only include the surface mount components. - - `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. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `position` output. + * Valid keys: + - `format`: [string='ASCII'] [ASCII,CSV] format for the position file. + - `only_smd`: [boolean=true] only include the surface mount components. + - `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. * PS (Postscript) * Type: `ps` * Description: Exports the PCB to a format suitable for printing. This output is what you get from the File/Plot menu in pcbnew. - * Options: - - `a4_output`: [boolean=true] force A4 paper size. - - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `line_width`: [number=0.15] [0.02,2] for objects without width [mm]. - - `mirror_plot`: [boolean=false] plot mirrored. - - `negative_plot`: [boolean=false] invert black and white. - - `plot_footprint_refs`: [boolean=true] include the footprint references. - - `plot_footprint_values`: [boolean=true] include the footprint values. - - `plot_sheet_reference`: [boolean=false] currently without effect. - - `scale_adjust_x`: [number=1.0] fine grain adjust for the X scale (floating point multiplier). - - `scale_adjust_y`: [number=1.0] fine grain adjust for the Y scale (floating point multiplier). - - `scaling`: [number=1] scale factor (0 means autoscaling). - - `sketch_plot`: [boolean=false] don't fill objects, just draw the outline. - - `tent_vias`: [boolean=true] cover the vias. - - `width_adjust`: [number=0] this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings. - Only used to plot pads and tracks. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `ps` output. + * Valid keys: + - `a4_output`: [boolean=true] force A4 paper size. + - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). + - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. + - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. + - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. + - `line_width`: [number=0.15] [0.02,2] for objects without width [mm]. + - `mirror_plot`: [boolean=false] plot mirrored. + - `negative_plot`: [boolean=false] invert black and white. + - `plot_footprint_refs`: [boolean=true] include the footprint references. + - `plot_footprint_values`: [boolean=true] include the footprint values. + - `plot_sheet_reference`: [boolean=false] currently without effect. + - `scale_adjust_x`: [number=1.0] fine grain adjust for the X scale (floating point multiplier). + - `scale_adjust_y`: [number=1.0] fine grain adjust for the Y scale (floating point multiplier). + - `scaling`: [number=1] scale factor (0 means autoscaling). + - `sketch_plot`: [boolean=false] don't fill objects, just draw the outline. + - `tent_vias`: [boolean=true] cover the vias. + - `width_adjust`: [number=0] this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings. + Only used to plot pads and tracks. * STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure) * Type: `step` * Description: Exports the PCB as a 3D model. This is the most common 3D format for exchange purposes. This output is what you get from the 'File/Export/STEP' menu in pcbnew. - * Options: - - `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. - - `origin`: [string='grid'] determines the coordinates origin. Using grid the coordinates are the same as you have in the design sheet. - 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=''] name for the generated STEP file (the name of the PCB if empty). + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `step` output. + * Valid keys: + - `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. + - `origin`: [string='grid'] determines the coordinates origin. Using grid the coordinates are the same as you have in the design sheet. + 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=''] name for the generated STEP file (the name of the PCB if empty). * SVG (Scalable Vector Graphics) * Type: `svg` * Description: Exports the PCB to a format suitable for 2D graphics software. Unlike bitmaps SVG drawings can be scaled without losing resolution. This output is what you get from the File/Plot menu in pcbnew. - * Options: - - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). - - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `line_width`: [number=0.25] [0.02,2] for objects without width [mm]. - - `mirror_plot`: [boolean=false] plot mirrored. - - `negative_plot`: [boolean=false] invert black and white. - - `plot_footprint_refs`: [boolean=true] include the footprint references. - - `plot_footprint_values`: [boolean=true] include the footprint values. - - `plot_sheet_reference`: [boolean=false] currently without effect. - - `tent_vias`: [boolean=true] cover the vias. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `layers`: [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot. + * Valid keys: + - `description`: [string=''] A description for the layer, for documentation purposes. + - `layer`: [string=''] Name of the layer. As you see it in KiCad. + - `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `svg` output. + * Valid keys: + - `drill_marks`: [string='full'] what to use to indicate the drill places, can be none, small or full (for real scale). + - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. + - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. + - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. + - `line_width`: [number=0.25] [0.02,2] for objects without width [mm]. + - `mirror_plot`: [boolean=false] plot mirrored. + - `negative_plot`: [boolean=false] invert black and white. + - `plot_footprint_refs`: [boolean=true] include the footprint references. + - `plot_footprint_values`: [boolean=true] include the footprint values. + - `plot_sheet_reference`: [boolean=false] currently without effect. + - `tent_vias`: [boolean=true] cover the vias. ## Using KiPlot @@ -544,7 +738,7 @@ Usage: kiplot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE] [-q | -v...] [-i] [TARGET...] kiplot [-c PLOT_CONFIG] --list - kiplot [-b BOARD] [-d OUT_DIR] [-p] --example + kiplot [-b BOARD] [-d OUT_DIR] [-p | -P] --example kiplot [-v] --help-list-outputs kiplot --help-output=HELP_OUTPUT kiplot --help-outputs @@ -568,6 +762,7 @@ Options: -i, --invert-sel Generate the outputs not listed as targets -l, --list List available outputs (in the config file) -p, --copy-options Copy plot options from the PCB file + -P, --copy-and-expand As -p but expand the list of layers -q, --quiet Remove information logs -s PRE, --skip-pre PRE Skip preflights, comma separated or `all` -v, --verbose Show debugging information diff --git a/docs/README.in b/docs/README.in index 3bde2781..8d4b6177 100644 --- a/docs/README.in +++ b/docs/README.in @@ -194,14 +194,88 @@ outputs: use_gerber_net_attributes: false layers: - - layer: F.Cu - suffix: F_Cu - - layer: B.Cu - suffix: B_Cu + - 'F.Cu' + - 'B.Cu' ``` Most options are the same you'll find in the KiCad dialogs. + +### Specifying the layers + +You have various ways to specify the layers. If you need to specify just one layer you can just use its name: + +``` + layers: 'F.Cu' +``` + +If you want to specify all the available layers: + +``` + layers: 'all' +``` + +You can also select the layers you want in KiCad (using File, Plot dialog) and save your PCB. +Then you just need to use: + +``` + layers: 'selected' +``` + +You can also use any of the following grup of layers: + +- **copper** all the copper layers +- **technical** all the technical layers (silk sreen, solder mask, paste, adhesive, etc.) +- **user** all the user layers (draw, comments, eco, margin, edge cuts, etc.) + +You can also mix the above definitions using a list: + +``` + layers: + - 'copper' + - 'Dwgs.User' +``` + +This will select all the copper layers and the user drawings. +Note that the above mentioned options will use file name suffixes and descriptions selected automatically. +If you want to use a particular suffix and provide better descriptions you can use the following format: + +``` + layers: + - layer: 'F.Cu' + suffix: 'F_Cu' + description: 'Front copper' + - layer: 'B.Cu' + suffix: 'B_Cu' + description: 'Bottom copper' +``` + +You can also mix the styles: + +``` + layers: + - 'copper' + - layer: 'Cmts.User' + suffix: 'Cmts_User' + description: 'User comments' + - 'Dwgs.User' +``` + +If you need to use the same list of layers for various outputs you can use YAML anchors. +The first time you define the list of layers just assign an ancho, here is an example: + +``` + layers: &copper_and_cmts + - copper + - 'Cmts.User' +``` + +Next time you need this list just use an alias, like this: + +``` + layers: *copper_and_cmts +``` + ### @outputs@ ## Using KiPlot diff --git a/docs/samples/generic_plot.kiplot.yaml b/docs/samples/generic_plot.kiplot.yaml index 148b8d5a..bd2ab973 100644 --- a/docs/samples/generic_plot.kiplot.yaml +++ b/docs/samples/generic_plot.kiplot.yaml @@ -50,67 +50,7 @@ outputs: tent_vias: true # [boolean=false] use the auxiliar axis as origin for coordinates use_aux_axis_as_origin: false - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all # Excellon drill format: # You can create a map file for documentation purposes. @@ -198,67 +138,7 @@ outputs: use_gerber_x2_attributes: true # [boolean=false] use legacy Protel file extensions use_protel_extensions: false - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all # HPGL (Hewlett & Packard Graphics Language): # This output is what you get from the File/Plot menu in pcbnew. @@ -295,67 +175,7 @@ outputs: sketch_plot: false # [boolean=true] cover the vias tent_vias: true - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all # IBoM (Interactive HTML BoM): # For more information: https://github.com/INTI-CMNB/InteractiveHtmlBom @@ -474,67 +294,7 @@ outputs: plot_sheet_reference: false # [boolean=true] cover the vias tent_vias: true - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all # PDF PCB Print (Portable Document Format): # This is the main format to document your PCB. @@ -546,67 +306,7 @@ outputs: options: # [string=''] filename for the output PDF (the name of the PCB if empty) output_name: '' - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all # PDF Schematic Print (Portable Document Format): # This is the main format to document your schematic. @@ -677,67 +377,7 @@ outputs: # [number=0] this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings. # Only used to plot pads and tracks width_adjust: 0 - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all # STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure): # This is the most common 3D format for exchange purposes. @@ -790,65 +430,5 @@ outputs: plot_sheet_reference: false # [boolean=true] cover the vias tent_vias: true - layers: - - layer: 'F.Cu' - suffix: 'F_Cu' - description: 'Front copper' - - layer: 'B.Cu' - suffix: 'B_Cu' - description: 'Bottom copper' - - layer: 'F.Adhes' - suffix: 'F_Adhes' - description: 'Front adhesive (glue)' - - layer: 'B.Adhes' - suffix: 'B_Adhes' - description: 'Bottom adhesive (glue)' - - layer: 'F.Paste' - suffix: 'F_Paste' - description: 'Front solder paste' - - layer: 'B.Paste' - suffix: 'B_Paste' - description: 'Bottom solder paste' - - layer: 'F.SilkS' - suffix: 'F_SilkS' - description: 'Front silkscreen (artwork)' - - layer: 'B.SilkS' - suffix: 'B_SilkS' - description: 'Bottom silkscreen (artwork)' - - layer: 'F.Mask' - suffix: 'F_Mask' - description: 'Front soldermask (negative)' - - layer: 'B.Mask' - suffix: 'B_Mask' - description: 'Bottom soldermask (negative)' - - layer: 'Dwgs.User' - suffix: 'Dwgs_User' - description: 'User drawings' - - layer: 'Cmts.User' - suffix: 'Cmts_User' - description: 'User comments' - - layer: 'Eco1.User' - suffix: 'Eco1_User' - description: 'For user usage 1' - - layer: 'Eco2.User' - suffix: 'Eco2_User' - description: 'For user usage 2' - - layer: 'Edge.Cuts' - suffix: 'Edge_Cuts' - description: 'Board shape' - - layer: 'Margin' - suffix: 'Margin' - description: 'Margin relative to edge cut' - - layer: 'F.CrtYd' - suffix: 'F_CrtYd' - description: 'Front courtyard area' - - layer: 'B.CrtYd' - suffix: 'B_CrtYd' - description: 'Bottom courtyard area' - - layer: 'F.Fab' - suffix: 'F_Fab' - description: 'Front documentation' - - layer: 'B.Fab' - suffix: 'B_Fab' - description: 'Bottom documentation' + layers: all diff --git a/kiplot/__main__.py b/kiplot/__main__.py index 17df82d4..c7b7f106 100644 --- a/kiplot/__main__.py +++ b/kiplot/__main__.py @@ -5,7 +5,7 @@ Usage: kiplot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE] [-q | -v...] [-i] [TARGET...] kiplot [-c PLOT_CONFIG] --list - kiplot [-b BOARD] [-d OUT_DIR] [-p] --example + kiplot [-b BOARD] [-d OUT_DIR] [-p | -P] --example kiplot [-v] --help-list-outputs kiplot --help-output=HELP_OUTPUT kiplot --help-outputs @@ -29,6 +29,7 @@ Options: -i, --invert-sel Generate the outputs not listed as targets -l, --list List available outputs (in the config file) -p, --copy-options Copy plot options from the PCB file + -P, --copy-and-expand As -p but expand the list of layers -q, --quiet Remove information logs -s PRE, --skip-pre PRE Skip preflights, comma separated or `all` -v, --verbose Show debugging information @@ -179,7 +180,7 @@ def main(): if args.copy_options and not args.board_file: logger.error('Asked to copy options but no PCB specified.') sys.exit(EXIT_BAD_ARGS) - create_example(args.board_file, GS.out_dir, args.copy_options) + create_example(args.board_file, GS.out_dir, args.copy_options, args.copy_and_expand) sys.exit(0) # Determine the YAML file diff --git a/kiplot/config_reader.py b/kiplot/config_reader.py index bbaf0bea..38edd1f7 100644 --- a/kiplot/config_reader.py +++ b/kiplot/config_reader.py @@ -3,17 +3,16 @@ Class to read KiPlot config files """ import os -from sys import (exit, maxsize, exc_info) -from traceback import print_tb +from sys import (exit, maxsize) from collections import OrderedDict -from .error import (KiPlotConfigurationError) -from .gs import GS -from .kiplot import (Layer, load_board) -from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE) -from .optionable import Optionable -from .out_base import BaseOutput +from .error import (KiPlotConfigurationError, config_error) +from .kiplot import (load_board) +from .misc import (NO_YAML_MODULE, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE) + +from .reg_out import RegOutput from .pre_base import BasePreFlight + # Logger from . import log @@ -27,15 +26,6 @@ except ImportError: # pragma: no cover exit(NO_YAML_MODULE) -def config_error(msg): - if GS.debug_enabled: - logger.error('Trace stack:') - (type, value, traceback) = exc_info() - print_tb(traceback) - logger.error(msg) - exit(EXIT_BAD_CONFIG) - - class CfgYamlReader(object): def __init__(self): super().__init__() @@ -51,76 +41,28 @@ class CfgYamlReader(object): config_error("Unknown KiPlot config version: "+str(version)) return version - def _parse_layers(self, layers_to_parse): - # Check we have a list of layers - if not isinstance(layers_to_parse, list): - raise KiPlotConfigurationError("`layers` must be a list") - # Parse the elements - layers = [] - for l in layers_to_parse: - # Extract the attributes - layer = None - description = 'no desc' - suffix = '' - for k, v in l.items(): - if k == 'layer': - layer = str(v) - elif k == 'description': - description = str(v) - elif k == 'suffix': - suffix = str(v) - else: - raise KiPlotConfigurationError("Unknown `{}` attribute for `layer`".format(k)) - # Check we got the layer name - if layer is None: - raise KiPlotConfigurationError("Missing `layer` attribute for layer entry ({})".format(l)) - # Create an object for it - layers.append(Layer(layer, suffix, description)) - return layers - def _parse_output(self, o_obj): - # Default values - name = None - desc = None - otype = None - options = None - outdir = '.' - layers = [] - # Parse all of them - for k, v in o_obj.items(): - if k == 'name': - name = v - elif k == 'comment': - desc = v - elif k == 'type': - otype = v - elif k == 'options': - options = v - elif k == 'dir': - outdir = v - elif k == 'layers': - layers = v - else: - config_error("Unknown key `{}` in `{}` ({})".format(k, name, otype)) - # Validate them - if not name: + try: + name = o_obj['name'] + except KeyError: config_error("Output needs a name in: "+str(o_obj)) - if not otype: + + try: + otype = o_obj['type'] + except KeyError: config_error("Output `"+name+"` needs a type") + name_type = "`"+name+"` ("+otype+")" # Is a valid type? - if not BaseOutput.is_registered(otype): + if not RegOutput.is_registered(otype): config_error("Unknown output type: `{}`".format(otype)) # Load it logger.debug("Parsing output options for "+name_type) - o_out = BaseOutput.get_class_for(otype)(name, otype, desc) + o_out = RegOutput.get_class_for(otype)() # Apply the options try: - # If we have layers parse them - if layers: - layers = self._parse_layers(layers) - o_out.config(outdir, options, layers) + o_out.config(o_obj) except KiPlotConfigurationError as e: config_error("In section '"+name+"' ("+otype+"): "+str(e)) @@ -207,8 +149,6 @@ class CfgYamlReader(object): def trim(docstring): """ PEP 257 recommended trim for __doc__ """ - if not docstring: - return '' # Convert tabs to spaces (following the normal Python rules) # and split into a list of lines: lines = docstring.expandtabs().splitlines() @@ -234,16 +174,16 @@ def trim(docstring): def print_output_options(name, cl, indent): ind_str = indent*' ' - if issubclass(cl, BaseOutput): - obj = cl('', name, '') - else: - obj = cl(name, '') - print(ind_str+'* Options:') + obj = cl() + print(ind_str+'* Valid keys:') num_opts = 0 - for k, v in Optionable.get_attrs_gen(obj): + for k, v in obj.get_attrs_gen(): + if k == 'type': + # Type is fixed for an output + continue help = getattr(obj, '_help_'+k) if help is None: - help = 'Undocumented' + help = 'Undocumented' # pragma: no cover lines = help.split('\n') preface = ind_str+' - `{}`: '.format(k) clines = len(lines) @@ -261,7 +201,7 @@ def print_output_options(name, cl, indent): def print_one_out_help(details, n, o): lines = trim(o.__doc__) if len(lines) == 0: - lines = ['Undocumented', 'No description'] + lines = ['Undocumented', 'No description'] # pragma: no cover if details: print('* '+lines[0]) print(' * Type: `{}`'.format(n)) @@ -274,7 +214,7 @@ def print_one_out_help(details, n, o): def print_outputs_help(details=False): - outs = BaseOutput.get_registered() + outs = RegOutput.get_registered() logger.debug('{} supported outputs'.format(len(outs))) print('Supported outputs:') for n, o in OrderedDict(sorted(outs.items())).items(): @@ -284,10 +224,10 @@ def print_outputs_help(details=False): def print_output_help(name): - if not BaseOutput.is_registered(name): + if not RegOutput.is_registered(name): logger.error('Unknown output type `{}`, try --help-list-outputs'.format(name)) exit(EXIT_BAD_ARGS) - print_one_out_help(True, name, BaseOutput.get_class_for(name)) + print_one_out_help(True, name, RegOutput.get_class_for(name)) def print_preflights_help(): @@ -297,19 +237,16 @@ def print_preflights_help(): for n, o in pres.items(): help = o.__doc__ if help is None: - help = 'Undocumented' + help = 'Undocumented' # pragma: no cover print('- {}: {}.'.format(n, help.rstrip())) def print_example_options(f, cls, name, indent, po): ind_str = indent*' ' - if issubclass(cls, BaseOutput): - obj = cls('', name, '') - else: - obj = cls(name, '') + obj = cls() if po: obj.read_vals_from_po(po) - for k, v in Optionable.get_attrs_gen(obj): + for k, v in obj.get_attrs_gen(): help = getattr(obj, '_help_'+k) if help: help_lines = help.split('\n') @@ -328,7 +265,7 @@ def print_example_options(f, cls, name, indent, po): return obj -def create_example(pcb_file, out_dir, copy_options): +def create_example(pcb_file, out_dir, copy_options, copy_expand): if not os.path.exists(out_dir): os.makedirs(out_dir) fname = os.path.join(out_dir, EXAMPLE_CFG) @@ -346,26 +283,22 @@ def create_example(pcb_file, out_dir, copy_options): f.write(' #'+o.__doc__.rstrip()+'\n') f.write(' {}: {}\n'.format(n, o.get_example())) # Outputs - outs = BaseOutput.get_registered() + outs = RegOutput.get_registered() f.write('\noutputs:\n') # List of layers po = None + layers = 'all' if pcb_file: # We have a PCB to take as reference board = load_board(pcb_file) - if copy_options: + if copy_options or copy_expand: # Layers and plot options from the PCB - layers = Layer.get_plot_layers() + layers = 'selected' po = board.GetPlotOptions() - else: - layers = Layer.get_pcb_layers() - else: - # Use the default list of layers - layers = Layer.get_default_layers() for n, cls in OrderedDict(sorted(outs.items())).items(): lines = trim(cls.__doc__) if len(lines) == 0: - lines = ['Undocumented', 'No description'] + lines = ['Undocumented', 'No description'] # pragma: no cover f.write(' # '+lines[0].rstrip()+':\n') for ln in range(2, len(lines)): f.write(' # '+lines[ln].rstrip()+'\n') @@ -374,12 +307,17 @@ def create_example(pcb_file, out_dir, copy_options): f.write(" type: '{}'\n".format(n)) f.write(" dir: 'Example/{}_dir'\n".format(n)) f.write(" options:\n") - obj = print_example_options(f, cls, n, 6, po) - if '_layers' in obj.__dict__: - f.write(' layers:\n') - for layer in layers: - f.write(" - layer: '{}'\n".format(layer.name)) - f.write(" suffix: '{}'\n".format(layer.suffix)) - if layer.desc: - f.write(" description: '{}'\n".format(layer.desc)) + obj = cls() + print_example_options(f, obj.options, n, 6, po) + if 'layers' in obj.__dict__: + if copy_expand: + f.write(' layers:\n') + layers = obj.layers.solve(layers) + for layer in layers: + f.write(" - layer: '{}'\n".format(layer.layer)) + f.write(" suffix: '{}'\n".format(layer.suffix)) + if layer.description: + f.write(" description: '{}'\n".format(layer.description)) + else: + f.write(' layers: {}\n'.format(layers)) f.write('\n') diff --git a/kiplot/drill_marks.py b/kiplot/drill_marks.py index 4ecb84b2..82620253 100644 --- a/kiplot/drill_marks.py +++ b/kiplot/drill_marks.py @@ -1,12 +1,13 @@ from pcbnew import (PCB_PLOT_PARAMS) from .error import KiPlotConfigurationError -from kiplot.macros import macros, document, output_class # noqa: F401 +from .out_any_layer import AnyLayerOptions +from kiplot.macros import macros, document # noqa: F401 from . import log logger = log.get_logger(__name__) -class DrillMarks(object): +class DrillMarks(AnyLayerOptions): """ This class provides the drill_marks attribute. Used by DXF, HPGL, PDF, PS and SVG formats. """ # Mappings to KiCad values @@ -37,12 +38,15 @@ class DrillMarks(object): raise KiPlotConfigurationError("Unknown drill mark type: {}".format(val)) self._drill_marks = val - def config(self): + def config(self, tree): + super().config(tree) self._drill_marks = DrillMarks._drill_marks_map[self._drill_marks] def _configure_plot_ctrl(self, po, output_dir): + super()._configure_plot_ctrl(po, output_dir) # How we draw drill marks po.SetDrillMarksType(self._drill_marks) def read_vals_from_po(self, po): + super().read_vals_from_po(po) self._drill_marks = DrillMarks._drill_marks_rev_map[po.GetDrillMarksType()] diff --git a/kiplot/error.py b/kiplot/error.py index 473ddae3..64f6b2ac 100644 --- a/kiplot/error.py +++ b/kiplot/error.py @@ -1,6 +1,14 @@ """ KiPlot errors """ +from sys import (exit, exc_info) +from traceback import print_tb +from .gs import GS +from .misc import (EXIT_BAD_CONFIG) +# Logger +from . import log + +logger = log.get_logger(__name__) class KiPlotError(Exception): @@ -13,3 +21,12 @@ class PlotError(KiPlotError): class KiPlotConfigurationError(KiPlotError): pass + + +def config_error(msg): + if GS.debug_enabled: + logger.error('Trace stack:') + (type, value, traceback) = exc_info() + print_tb(traceback) + logger.error(msg) + exit(EXIT_BAD_CONFIG) diff --git a/kiplot/gs.py b/kiplot/gs.py index ec53071e..6ab4b5e8 100644 --- a/kiplot/gs.py +++ b/kiplot/gs.py @@ -14,6 +14,7 @@ class GS(object): sch_file = None out_dir = None filter_file = None + board = None debug_enabled = False @staticmethod diff --git a/kiplot/kiplot.py b/kiplot/kiplot.py index 6d1ec8fe..8a777ceb 100644 --- a/kiplot/kiplot.py +++ b/kiplot/kiplot.py @@ -14,7 +14,7 @@ from importlib.util import (spec_from_file_location, module_from_spec) from .gs import (GS) from .misc import (PLOT_ERROR, NO_PCBNEW_MODULE, MISSING_TOOL, CMD_EESCHEMA_DO, URL_EESCHEMA_DO, NO_SCH_FILE, CORRUPTED_PCB, EXIT_BAD_ARGS) -from .error import (PlotError) +from .error import (PlotError, KiPlotConfigurationError, config_error) from .pre_base import BasePreFlight from . import log @@ -31,134 +31,6 @@ except ImportError: # pragma: no cover exit(NO_PCBNEW_MODULE) -class Layer(object): - """ A layer description """ - # Default names - DEFAULT_LAYER_NAMES = { - 'F.Cu': pcbnew.F_Cu, - 'B.Cu': pcbnew.B_Cu, - 'F.Adhes': pcbnew.F_Adhes, - 'B.Adhes': pcbnew.B_Adhes, - 'F.Paste': pcbnew.F_Paste, - 'B.Paste': pcbnew.B_Paste, - 'F.SilkS': pcbnew.F_SilkS, - 'B.SilkS': pcbnew.B_SilkS, - 'F.Mask': pcbnew.F_Mask, - 'B.Mask': pcbnew.B_Mask, - 'Dwgs.User': pcbnew.Dwgs_User, - 'Cmts.User': pcbnew.Cmts_User, - 'Eco1.User': pcbnew.Eco1_User, - 'Eco2.User': pcbnew.Eco2_User, - 'Edge.Cuts': pcbnew.Edge_Cuts, - 'Margin': pcbnew.Margin, - 'F.CrtYd': pcbnew.F_CrtYd, - 'B.CrtYd': pcbnew.B_CrtYd, - 'F.Fab': pcbnew.F_Fab, - 'B.Fab': pcbnew.B_Fab, - } - # Default names - DEFAULT_LAYER_DESC = { - 'F.Cu': 'Front copper', - 'B.Cu': 'Bottom copper', - 'F.Adhes': 'Front adhesive (glue)', - 'B.Adhes': 'Bottom adhesive (glue)', - 'F.Paste': 'Front solder paste', - 'B.Paste': 'Bottom solder paste', - 'F.SilkS': 'Front silkscreen (artwork)', - 'B.SilkS': 'Bottom silkscreen (artwork)', - 'F.Mask': 'Front soldermask (negative)', - 'B.Mask': 'Bottom soldermask (negative)', - 'Dwgs.User': 'User drawings', - 'Cmts.User': 'User comments', - 'Eco1.User': 'For user usage 1', - 'Eco2.User': 'For user usage 2', - 'Edge.Cuts': 'Board shape', - 'Margin': 'Margin relative to edge cut', - 'F.CrtYd': 'Front courtyard area', - 'B.CrtYd': 'Bottom courtyard area', - 'F.Fab': 'Front documentation', - 'B.Fab': 'Bottom documentation', - } - # Names from the board file - pcb_layers = {} - plot_layers = {} - - def __init__(self, name, suffix, desc): - self.id = pcbnew.UNDEFINED_LAYER - self.is_inner = False - self.name = name - self.suffix = suffix - if desc is None and name in Layer.DEFAULT_LAYER_DESC: - desc = Layer.DEFAULT_LAYER_DESC[name] - self.desc = desc - - @staticmethod - def set_pcb_layers(board): - for id in board.GetEnabledLayers().Seq(): - Layer.pcb_layers[board.GetLayerName(id)] = id - - @staticmethod - def _get_layers(d_layers): - layers = [] - for n, id in d_layers.items(): - s = n.replace('.', '_') - d = Layer.DEFAULT_LAYER_DESC.get(n) - layers.append(Layer(n, s, d)) - return layers - - @staticmethod - def get_pcb_layers(): - return Layer._get_layers(Layer.pcb_layers) - - @staticmethod - def set_plot_layers(board): - enabled = board.GetEnabledLayers().Seq() - for id in board.GetPlotOptions().GetLayerSelection().Seq(): - if id in enabled: - Layer.plot_layers[board.GetLayerName(id)] = id - - @staticmethod - def get_plot_layers(): - return Layer._get_layers(Layer.plot_layers) - - def get_layer_id_from_name(self, layer_cnt): - """ Get the pcbnew layer from the string provided in the config """ - # Priority - # 1) Internal list - if self.name in Layer.DEFAULT_LAYER_NAMES: - self.id = Layer.DEFAULT_LAYER_NAMES[self.name] - else: - id = Layer.pcb_layers.get(self.name) - if id is not None: - # 2) List from the PCB - self.id = id - self.is_inner = id < pcbnew.B_Cu - elif self.name.startswith("Inner"): - # 3) Inner.N names - m = re.match(r"^Inner\.([0-9]+)$", self.name) - if not m: - raise PlotError("Malformed inner layer name: {}, use Inner.N".format(self.name)) - self.id = int(m.group(1)) - self.is_inner = True - else: - raise PlotError("Unknown layer name: "+self.name) - # Check if the layer is in use - if self.is_inner and (self.id < 1 or self.id >= layer_cnt - 1): - raise PlotError("Inner layer `{}` is not valid for this board".format(self)) - return self.id - - @staticmethod - def get_default_layers(): - layers = [] - for n, d in Layer.DEFAULT_LAYER_DESC.items(): - s = n.replace('.', '_') - layers.append(Layer(n, s, d)) - return layers - - def __str__(self): - return "{} ({} '{}' {})".format(self.name, self.id, self.desc, self.suffix) - - def _import(name, path): # Python 3.4+ import mechanism spec = spec_from_file_location("kiplot."+name, path) @@ -178,6 +50,7 @@ def _load_actions(path): def load_actions(): """ Load all the available ouputs and preflights """ from mcpy import activate + # activate.activate() _load_actions(os.path.abspath(os.path.dirname(__file__))) home = os.environ.get('HOME') if home: @@ -229,9 +102,7 @@ def load_board(pcb_file=None): board = pcbnew.LoadBoard(pcb_file) if BasePreFlight.get_option('check_zone_fills'): pcbnew.ZONE_FILLER(board).Fill(board.Zones()) - # Now we know the names of the layers for this board - Layer.set_pcb_layers(board) - Layer.set_plot_layers(board) + GS.board = board except OSError as e: logger.error('Error loading PCB file. Currupted?') logger.error(e) @@ -290,7 +161,7 @@ def generate_outputs(outputs, target, invert, skip_pre): # Generate outputs board = None for out in outputs: - if (n == 0) or ((out.get_name() in target) ^ invert): + if (n == 0) or ((out.name in target) ^ invert): logger.info('- '+str(out)) # Should we load the PCB? if out.is_pcb() and (board is None): @@ -298,9 +169,11 @@ def generate_outputs(outputs, target, invert, skip_pre): if out.is_sch(): GS.check_sch() try: - out.run(get_output_dir(out.get_outdir()), board) + out.run(get_output_dir(out.dir), board) except PlotError as e: logger.error("In output `"+str(out)+"`: "+str(e)) exit(PLOT_ERROR) + except KiPlotConfigurationError as e: + config_error("In section '"+out.name+"' ("+out.type+"): "+str(e)) else: logger.debug('Skipping `%s` output', str(out)) diff --git a/kiplot/layer.py b/kiplot/layer.py new file mode 100644 index 00000000..6feea867 --- /dev/null +++ b/kiplot/layer.py @@ -0,0 +1,211 @@ +import pcbnew +from .optionable import Optionable +from .gs import GS +from re import match + +from .error import (PlotError, KiPlotConfigurationError) +from kiplot.macros import macros, document, output_class # noqa: F401 + + +class Layer(Optionable): + """ A layer description """ + # Default names + DEFAULT_LAYER_NAMES = { + 'F.Cu': pcbnew.F_Cu, + 'B.Cu': pcbnew.B_Cu, + 'F.Adhes': pcbnew.F_Adhes, + 'B.Adhes': pcbnew.B_Adhes, + 'F.Paste': pcbnew.F_Paste, + 'B.Paste': pcbnew.B_Paste, + 'F.SilkS': pcbnew.F_SilkS, + 'B.SilkS': pcbnew.B_SilkS, + 'F.Mask': pcbnew.F_Mask, + 'B.Mask': pcbnew.B_Mask, + 'Dwgs.User': pcbnew.Dwgs_User, + 'Cmts.User': pcbnew.Cmts_User, + 'Eco1.User': pcbnew.Eco1_User, + 'Eco2.User': pcbnew.Eco2_User, + 'Edge.Cuts': pcbnew.Edge_Cuts, + 'Margin': pcbnew.Margin, + 'F.CrtYd': pcbnew.F_CrtYd, + 'B.CrtYd': pcbnew.B_CrtYd, + 'F.Fab': pcbnew.F_Fab, + 'B.Fab': pcbnew.B_Fab, + } + # Default names + DEFAULT_LAYER_DESC = { + 'F.Cu': 'Front copper', + 'B.Cu': 'Bottom copper', + 'F.Adhes': 'Front adhesive (glue)', + 'B.Adhes': 'Bottom adhesive (glue)', + 'F.Paste': 'Front solder paste', + 'B.Paste': 'Bottom solder paste', + 'F.SilkS': 'Front silkscreen (artwork)', + 'B.SilkS': 'Bottom silkscreen (artwork)', + 'F.Mask': 'Front soldermask (negative)', + 'B.Mask': 'Bottom soldermask (negative)', + 'Dwgs.User': 'User drawings', + 'Cmts.User': 'User comments', + 'Eco1.User': 'For user usage 1', + 'Eco2.User': 'For user usage 2', + 'Edge.Cuts': 'Board shape', + 'Margin': 'Margin relative to edge cut', + 'F.CrtYd': 'Front courtyard area', + 'B.CrtYd': 'Bottom courtyard area', + 'F.Fab': 'Front documentation', + 'B.Fab': 'Bottom documentation', + } + # Names from the board file + _pcb_layers = None + _plot_layers = None + + def __init__(self): + super().__init__() + with document: + self.layer = '' + """ Name of the layer. As you see it in KiCad """ + self.suffix = '' + """ Suffix used in file names related to this layer. Derived from the name if not specified """ + self.description = '' + """ A description for the layer, for documentation purposes """ + self._unkown_is_error = True + + def config(self, tree): + super().config(tree) + if not self.layer: + raise KiPlotConfigurationError("Missing or empty `layer` attribute for layer entry ({})".format(tree)) + if not self.description: + if self.layer in Layer.DEFAULT_LAYER_DESC: + self.description = Layer.DEFAULT_LAYER_DESC[self.layer] + else: + self.description = 'No description' + if not self.suffix: + self.suffix = self.layer.replace('.', '_') + + @property + def id(self): + return self._id + + @staticmethod + def solve(values): + board = GS.board + layer_cnt = 2 + if board: + layer_cnt = board.GetCopperLayerCount() + # Get the list of used layers from the board + # Used for 'all' but also to validate the layer names + if Layer._pcb_layers is None: + Layer._pcb_layers = {} + if board: + Layer._set_pcb_layers() + # Get the list of selected layers for plot operations from the board + if Layer._plot_layers is None: + Layer._plot_layers = {} + if board: + Layer._set_plot_layers() + # Solve string + if isinstance(values, str): + values = [values] + # Solve list + if isinstance(values, list): + new_vals = [] + for layer in values: + if isinstance(layer, Layer): + layer._get_layer_id_from_name() + # Check if the layer is in use + if layer._is_inner and (layer._id < 1 or layer._id >= layer_cnt - 1): + raise PlotError("Inner layer `{}` is not valid for this board".format(layer)) + new_vals.append(layer) + else: # A string + ext = None + if layer == 'all': + ext = Layer._get_layers(Layer._pcb_layers) + elif layer == 'selected': + ext = Layer._get_layers(Layer._plot_layers) + elif layer == 'copper': + ext = Layer._get_layers(Layer._get_copper()) + elif layer == 'technical': + ext = Layer._get_layers(Layer._get_technical()) + elif layer == 'user': + ext = Layer._get_layers(Layer._get_user()) + elif layer in Layer._pcb_layers: + ext = [Layer.create_layer(layer)] + if ext is None: + raise KiPlotConfigurationError("Unknown layer spec: `{}`".format(layer)) + new_vals.extend(ext) + return new_vals + assert False, "Unimplemented layer type "+str(type(values)) # pragma: no cover + + @staticmethod + def _get_copper(): + return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().CuStack()} + + @staticmethod + def _get_technical(): + return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().Technicals()} + + @staticmethod + def _get_user(): + return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().Users()} + + @staticmethod + def _set_pcb_layers(): + Layer._pcb_layers = {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().Seq()} + + @classmethod + def create_layer(cls, name): + layer = cls() + layer.layer = name + layer.suffix = name.replace('.', '_') + layer.description = Layer.DEFAULT_LAYER_DESC.get(name) + layer._get_layer_id_from_name() + return layer + + @staticmethod + def _get_layers(d_layers): + layers = [] + for n, id in d_layers.items(): + layers.append(Layer.create_layer(n)) + return layers + + @staticmethod + def _set_plot_layers(): + board = GS.board + enabled = board.GetEnabledLayers().Seq() + for id in board.GetPlotOptions().GetLayerSelection().Seq(): + if id in enabled: + Layer._plot_layers[board.GetLayerName(id)] = id + + def _get_layer_id_from_name(self): + """ Get the pcbnew layer from the string provided in the config """ + # Priority + # 1) Internal list + if self.layer in Layer.DEFAULT_LAYER_NAMES: + self._id = Layer.DEFAULT_LAYER_NAMES[self.layer] + self._is_inner = False + else: + id = Layer._pcb_layers.get(self.layer) + if id is not None: + # 2) List from the PCB + self._id = id + self._is_inner = id < pcbnew.B_Cu + elif self.layer.startswith("Inner"): + # 3) Inner.N names + m = match(r"^Inner\.([0-9]+)$", self.layer) + if not m: + raise KiPlotConfigurationError("Malformed inner layer name: `{}`, use Inner.N".format(self.layer)) + self._id = int(m.group(1)) + self._is_inner = True + else: + raise KiPlotConfigurationError("Unknown layer name: `{}`".format(self.layer)) + return self._id + + def get_id(self, layer_cnt): + """ Returns the ID, also checks this is a valid inner layer """ + # Check if the layer is in use + if self._is_inner and (self._id < 1 or self._id >= layer_cnt - 1): + raise PlotError("Inner layer `{}` is not valid for this board".format(self)) + return self._id + + def __str__(self): + return "{} ({} '{}' {})".format(self.layer, self._id, self.description, self.suffix) diff --git a/kiplot/optionable.py b/kiplot/optionable.py index 57caa289..54d7fe63 100644 --- a/kiplot/optionable.py +++ b/kiplot/optionable.py @@ -11,16 +11,14 @@ def filter(v): class Optionable(object): - """ A class to validate and hold configuration options. + """ A class to validate and hold configuration outputs/options. Is configured from a dict and the collected values are stored in its attributes. """ _str_values_re = compile(r"\[string=.*\] \[([^\]]+)\]") _num_range_re = compile(r"\[number=.*\] \[(-?\d+),(-?\d+)\]") - def __init__(self, name, description): + def __init__(self): super().__init__() - self._name = name - self._description = description - self._unkown_is_error = True + self._unkown_is_error = False @staticmethod def _check_str(key, val, doc): @@ -61,13 +59,13 @@ class Optionable(object): if isinstance(v, dict): return 'dict' if isinstance(v, list): - return 'list' + return 'list({})'.format(Optionable._typeof(v[0])) return 'None' def _perform_config_mapping(self): """ Map the options to class attributes """ - attrs = Optionable.get_attrs_for(self) - for k, v in self._options.items(): + attrs = self.get_attrs_for() + for k, v in self._tree.items(): # Map known attributes and avoid mapping private ones if (k[0] == '_') or (k not in attrs): if self._unkown_is_error: @@ -105,24 +103,47 @@ class Optionable(object): # Dicts are solved using Optionable classes new_val = v # Create an object for the valid class - v = cur_val(k, cur_doc) + v = cur_val() # Delegate the validation to the object v.config(new_val) + elif isinstance(v, list): + new_val = [] + for element in v: + e_type = 'list('+Optionable._typeof(element)+')' + if e_type not in valid: + raise KiPlotConfigurationError("Option `{}` must be any of {} not `{}`". + format(element, valid, e_type)) + if isinstance(element, dict): + nv = cur_val() + nv.config(element) + new_val.append(nv) + else: + new_val.append(element) + v = new_val # Seems to be ok, map it setattr(self, k, v) - def config(self, options): - self._options = options - if options: + def config(self, tree): + self._tree = tree + if tree: self._perform_config_mapping() - @staticmethod - def get_attrs_for(obj): + def get_attrs_for(self): """ Returns all attributes """ - return dict(inspect.getmembers(obj, filter)) + return dict(inspect.getmembers(self, filter)) - @staticmethod - def get_attrs_gen(obj): + def get_attrs_gen(self): """ Returns a (key, val) iterator on public attributes """ - attrs = Optionable.get_attrs_for(obj) + attrs = self.get_attrs_for() return ((k, v) for k, v in attrs.items() if k[0] != '_') + + +class BaseOptions(Optionable): + """ A class to validate and hold output options. + Is configured from a dict and the collected values are stored in its attributes. """ + def __init__(self): + super().__init__() + + def read_vals_from_po(self, po): + """ Set attributes from a PCB_PLOT_PARAMS (plot options) """ + return diff --git a/kiplot/out_any_drill.py b/kiplot/out_any_drill.py index 94d2ccb6..a6c9cf2e 100644 --- a/kiplot/out_any_drill.py +++ b/kiplot/out_any_drill.py @@ -1,8 +1,7 @@ import os from pcbnew import (PLOT_FORMAT_HPGL, PLOT_FORMAT_POST, PLOT_FORMAT_GERBER, PLOT_FORMAT_DXF, PLOT_FORMAT_SVG, PLOT_FORMAT_PDF, wxPoint) -from .optionable import Optionable -from .out_base import BaseOutput +from .optionable import (Optionable, BaseOptions) from kiplot.macros import macros, document # noqa: F401 from . import log @@ -10,24 +9,26 @@ logger = log.get_logger(__name__) class DrillMap(Optionable): - def __init__(self, name, description): - super().__init__(name, description) + def __init__(self): + super().__init__() with document: self.type = 'pdf' - """ [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map """ + """ [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map """ # pragma: no cover + self._unkown_is_error = True class DrillReport(Optionable): - def __init__(self, name, description): - super().__init__(name, description) + def __init__(self): + super().__init__() with document: self.filename = '' - """ name of the drill report. Not generated unless a name is specified """ + """ name of the drill report. Not generated unless a name is specified """ # pragma: no cover + self._unkown_is_error = True -class AnyDrill(BaseOutput): - def __init__(self, name, type, description): - super().__init__(name, type, description) +class AnyDrill(BaseOptions): + def __init__(self): + super().__init__() # Options with document: self.use_aux_axis_as_origin = False @@ -47,8 +48,8 @@ class AnyDrill(BaseOutput): 'pdf': PLOT_FORMAT_PDF } - def config(self, outdir, options, layers): - super().config(outdir, options, layers) + def config(self, tree): + super().config(tree) # Solve the map for both cases if isinstance(self.map, str): self.map = self._map_map[self.map] diff --git a/kiplot/out_any_layer.py b/kiplot/out_any_layer.py index 08362c2e..e0953d92 100644 --- a/kiplot/out_any_layer.py +++ b/kiplot/out_any_layer.py @@ -3,19 +3,18 @@ from pcbnew import (GERBER_JOBFILE_WRITER, PLOT_CONTROLLER, IsCopperLayer) from .out_base import (BaseOutput) from .error import (PlotError, KiPlotConfigurationError) from .gs import (GS) +from .optionable import BaseOptions +from .layer import Layer from kiplot.macros import macros, document # noqa: F401 from . import log logger = log.get_logger(__name__) -class AnyLayer(BaseOutput): +class AnyLayerOptions(BaseOptions): """ Base class for: DXF, Gerber, HPGL, PDF, PS and SVG """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - # We need layers, so we define it - self._layers = None - # Options + def __init__(self): + super().__init__() with document: self.exclude_edge_layer = True """ do not include the PCB edge layer """ @@ -32,12 +31,6 @@ class AnyLayer(BaseOutput): self.tent_vias = True """ cover the vias """ # pragma: no cover - def config(self, outdir, options, layers): - super().config(outdir, options, layers) - # We need layers - if not self._layers: - raise KiPlotConfigurationError("Missing `layers` list") - def _configure_plot_ctrl(self, po, output_dir): logger.debug("Configuring plot controller for output") po.SetOutputDirectory(output_dir) @@ -53,15 +46,13 @@ class AnyLayer(BaseOutput): # We'll come back to this on a per-layer basis po.SetSkipPlotNPTH_Pads(False) - def run(self, output_dir, board): + def run(self, output_dir, board, layers): # fresh plot controller plot_ctrl = PLOT_CONTROLLER(board) # set up plot options for the whole output po = plot_ctrl.GetPlotOptions() self._configure_plot_ctrl(po, output_dir) - layer_cnt = board.GetCopperLayerCount() - # Gerber Job files aren't automagically created # We need to assist KiCad create_job = po.GetCreateGerberJobFile() @@ -70,11 +61,12 @@ class AnyLayer(BaseOutput): plot_ctrl.SetColorMode(True) + layers = Layer.solve(layers) # plot every layer in the output - for l in self._layers: + for l in layers: suffix = l.suffix - desc = l.desc - id = l.get_layer_id_from_name(layer_cnt) + desc = l.description + id = l.id # Set current layer plot_ctrl.SetLayer(id) # Skipping NPTH is controlled by whether or not this is @@ -116,3 +108,21 @@ class AnyLayer(BaseOutput): self.tent_vias = not po.GetPlotViaOnMaskLayer() # padsonsilk self.exclude_pads_from_silkscreen = not po.GetPlotPadsOnSilkLayer() + + +class AnyLayer(BaseOutput): + def __init__(self): + super().__init__() + with document: + self.layers = Layer + """ [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to plot """ # pragma: no cover + + def config(self, tree): + super().config(tree) + # We need layers + if isinstance(self.layers, type): + raise KiPlotConfigurationError("Missing `layers` list") + + def run(self, output_dir, board): + self.options.run(output_dir, board, self.layers) diff --git a/kiplot/out_base.py b/kiplot/out_base.py index e54ea97f..6987a585 100644 --- a/kiplot/out_base.py +++ b/kiplot/out_base.py @@ -1,45 +1,33 @@ -from .optionable import Optionable +from .reg_out import RegOutput +from kiplot.macros import macros, document # noqa: F401 from . import log logger = log.get_logger(__name__) -class BaseOutput(Optionable): +class BaseOutput(RegOutput): _registered = {} - def __init__(self, name, type, description): - super().__init__(name, description) - self._type = type + def __init__(self): + super().__init__() + with document: + self.name = '' + """ Used to identify this particular output definition """ + self.type = '' + """ Type of output """ + self.dir = '.' + """ Output directory for the generated files """ + self.comment = '' + """ A comment for documentation purposes """ # pragma: no cover self._sch_related = False - self._unkown_is_error = False - - def config(self, outdir, options, layers): - self._outdir = outdir - self._layers = layers - super().config(options) + self._unkown_is_error = True @staticmethod def attr2longopt(attr): return '--'+attr.replace('_', '-') - @staticmethod - def register(name, aclass): - BaseOutput._registered[name] = aclass - - @staticmethod - def is_registered(name): - return name in BaseOutput._registered - - @staticmethod - def get_class_for(name): - return BaseOutput._registered[name] - - @staticmethod - def get_registered(): - return BaseOutput._registered - def __str__(self): - return "'{}' ({}) [{}]".format(self._description, self._name, self._type) + return "'{}' ({}) [{}]".format(self.comment, self.name, self.type) def is_sch(self): """ True for outputs that works on the schematic """ @@ -49,17 +37,11 @@ class BaseOutput(Optionable): """ True for outputs that works on the PCB """ return not self._sch_related - def read_vals_from_po(self, po): - """ Set attributes from a PCB_PLOT_PARAMS (plot options) """ - return + def config(self, tree): + super().config(tree) + if getattr(self, 'options', None) and isinstance(self.options, type): + # No options, get the defaults + self.options = self.options() - # These get_* aren't really needed. - # _* members aren't supposed to be used by the user, not the code. - def get_name(self): - return self._name - - def get_outdir(self): - return self._outdir - - def run(self, output_dir, board): # pragma: no cover - logger.error("The run member for the class for the output type `{}` isn't implemented".format(self._type)) + def run(self, output_dir, board): + self.options.run(output_dir, board) diff --git a/kiplot/out_dxf.py b/kiplot/out_dxf.py index 2e4ef87d..3dbc258c 100644 --- a/kiplot/out_dxf.py +++ b/kiplot/out_dxf.py @@ -4,17 +4,9 @@ from kiplot.drill_marks import DrillMarks from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class DXF(AnyLayer, DrillMarks): - """ - DXF (Drawing Exchange Format) - Exports the PCB to 2D mechanical EDA tools (like AutoCAD). - This output is what you get from the File/Plot menu in pcbnew. """ - def __init__(self, name, type, description): - AnyLayer.__init__(self, name, type, description) - DrillMarks.__init__(self) - self._plot_format = PLOT_FORMAT_DXF - # Options +class DXFOptions(DrillMarks): + def __init__(self): + super().__init__() with document: self.use_aux_axis_as_origin = False """ use the auxiliar axis as origin for coordinates """ @@ -24,14 +16,10 @@ class DXF(AnyLayer, DrillMarks): """ use mm instead of inches """ self.sketch_plot = False """ don't fill objects, just draw the outline """ # pragma: no cover - - def config(self, outdir, options, layers): - AnyLayer.config(self, outdir, options, layers) - DrillMarks.config(self) + self._plot_format = PLOT_FORMAT_DXF def _configure_plot_ctrl(self, po, output_dir): - AnyLayer._configure_plot_ctrl(self, po, output_dir) - DrillMarks._configure_plot_ctrl(self, po, output_dir) + super()._configure_plot_ctrl(po, output_dir) po.SetDXFPlotPolygonMode(self.polygon_mode) # DXF_PLOTTER::DXF_UNITS isn't available # According to https://docs.kicad-pcb.org/doxygen/classDXF__PLOTTER.html 1 is mm @@ -40,9 +28,21 @@ class DXF(AnyLayer, DrillMarks): po.SetUseAuxOrigin(self.use_aux_axis_as_origin) def read_vals_from_po(self, po): - AnyLayer.read_vals_from_po(self, po) - DrillMarks.read_vals_from_po(self, po) + super().read_vals_from_po(po) self.polygon_mode = po.GetDXFPlotPolygonMode() self.metric_units = po.GetDXFPlotUnits() == 1 self.sketch_plot = po.GetPlotMode() == SKETCH self.use_aux_axis_as_origin = po.GetUseAuxOrigin() + + +@output_class +class DXF(AnyLayer): + """ + DXF (Drawing Exchange Format) + Exports the PCB to 2D mechanical EDA tools (like AutoCAD). + This output is what you get from the File/Plot menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = DXFOptions + """ [dict] Options for the `dxf` output """ # pragma: no cover diff --git a/kiplot/out_excellon.py b/kiplot/out_excellon.py index 2c33258e..d5fbc098 100644 --- a/kiplot/out_excellon.py +++ b/kiplot/out_excellon.py @@ -1,16 +1,11 @@ -from pcbnew import (EXCELLON_WRITER) -from .out_any_drill import (AnyDrill) +from pcbnew import EXCELLON_WRITER +from .out_any_drill import AnyDrill from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class Excellon(AnyDrill): - """ Excellon drill format - This is the main format for the drilling machine. - You can create a map file for documentation purposes. - This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) +class ExcellonOptions(AnyDrill): + def __init__(self): + super().__init__() with document: self.metric_units = True """ use metric units instead of inches """ @@ -26,3 +21,16 @@ class Excellon(AnyDrill): drill_writer.SetOptions(self.mirror_y_axis, self.minimal_header, offset, self.pth_and_npth_single_file) drill_writer.SetFormat(self.metric_units, EXCELLON_WRITER.DECIMAL_FORMAT) return drill_writer + + +@output_class +class Excellon(BaseOutput): # noqa: F821 + """ Excellon drill format + This is the main format for the drilling machine. + You can create a map file for documentation purposes. + This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = ExcellonOptions + """ [dict] Options for the `excellon` output """ # pragma: no cover diff --git a/kiplot/out_gerb_drill.py b/kiplot/out_gerb_drill.py index b323d8cd..5b29b3ae 100644 --- a/kiplot/out_gerb_drill.py +++ b/kiplot/out_gerb_drill.py @@ -1,16 +1,11 @@ -from pcbnew import (GERBER_WRITER) -from .out_any_drill import (AnyDrill) -from kiplot.macros import macros, output_class # noqa: F401 +from pcbnew import GERBER_WRITER +from .out_any_drill import AnyDrill +from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class Gerb_Drill(AnyDrill): - """ Gerber drill format - This is the information for the drilling machine in gerber format. - You can create a map file for documentation purposes. - This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) +class Gerb_DrillOptions(AnyDrill): + def __init__(self): + super().__init__() def _configure_writer(self, board, offset): drill_writer = GERBER_WRITER(board) @@ -18,3 +13,16 @@ class Gerb_Drill(AnyDrill): drill_writer.SetFormat(5) drill_writer.SetOptions(offset) return drill_writer + + +@output_class +class Gerb_Drill(BaseOutput): # noqa: F821 + """ Gerber drill format + This is the information for the drilling machine in gerber format. + You can create a map file for documentation purposes. + This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = Gerb_DrillOptions + """ [dict] Options for the `gerb_drill` output """ # pragma: no cover diff --git a/kiplot/out_gerber.py b/kiplot/out_gerber.py index 29ff9053..8b07d16d 100644 --- a/kiplot/out_gerber.py +++ b/kiplot/out_gerber.py @@ -1,18 +1,13 @@ from pcbnew import (PLOT_FORMAT_GERBER, FromMM, ToMM) -from .out_any_layer import (AnyLayer) +from .out_any_layer import (AnyLayer, AnyLayerOptions) from .error import KiPlotConfigurationError from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class Gerber(AnyLayer): - """ Gerber format - This is the main fabrication format for the PCB. - This output is what you get from the File/Plot menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) +class GerberOptions(AnyLayerOptions): + def __init__(self): + super().__init__() self._plot_format = PLOT_FORMAT_GERBER - # Options with document: self.use_aux_axis_as_origin = False """ use the auxiliar axis as origin for coordinates """ @@ -71,3 +66,15 @@ class Gerber(AnyLayer): self.use_aux_axis_as_origin = po.GetUseAuxOrigin() # linewidth self.line_width = ToMM(po.GetLineWidth()) + + +@output_class +class Gerber(AnyLayer): + """ Gerber format + This is the main fabrication format for the PCB. + This output is what you get from the File/Plot menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = GerberOptions + """ [dict] Options for the `gerber` output """ # pragma: no cover diff --git a/kiplot/out_hpgl.py b/kiplot/out_hpgl.py index 9857328b..79914a2c 100644 --- a/kiplot/out_hpgl.py +++ b/kiplot/out_hpgl.py @@ -5,15 +5,9 @@ from kiplot.drill_marks import DrillMarks from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class HPGL(AnyLayer, DrillMarks): - """ HPGL (Hewlett & Packard Graphics Language) - Exports the PCB for plotters and laser printers. - This output is what you get from the File/Plot menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._plot_format = PLOT_FORMAT_HPGL - # Options +class HPGLOptions(DrillMarks): + def __init__(self): + super().__init__() with document: self.mirror_plot = False """ plot mirrored """ @@ -27,14 +21,10 @@ class HPGL(AnyLayer, DrillMarks): """ [1,99] pen speed """ self.pen_width = 15 """ [0,100] pen diameter in MILS, useful to fill areas. However, it is in mm in HPGL files """ # pragma: no cover - - def config(self, outdir, options, layers): - AnyLayer.config(self, outdir, options, layers) - DrillMarks.config(self) + self._plot_format = PLOT_FORMAT_HPGL def _configure_plot_ctrl(self, po, output_dir): - AnyLayer._configure_plot_ctrl(self, po, output_dir) - DrillMarks._configure_plot_ctrl(self, po, output_dir) + super()._configure_plot_ctrl(po, output_dir) po.SetHPGLPenDiameter(self.pen_width) po.SetHPGLPenNum(self.pen_number) po.SetHPGLPenSpeed(self.pen_speed) @@ -49,8 +39,7 @@ class HPGL(AnyLayer, DrillMarks): po.SetScale(self.scaling) def read_vals_from_po(self, po): - AnyLayer.read_vals_from_po(self, po) - DrillMarks.read_vals_from_po(self, po) + super().read_vals_from_po(po) self.pen_width = po.GetHPGLPenDiameter() self.pen_number = po.GetHPGLPenNum() self.pen_speed = po.GetHPGLPenSpeed() @@ -61,3 +50,15 @@ class HPGL(AnyLayer, DrillMarks): self.scaling = AUTO_SCALE else: self.scaling = po.GetScale() + + +@output_class +class HPGL(AnyLayer): + """ HPGL (Hewlett & Packard Graphics Language) + Exports the PCB for plotters and laser printers. + This output is what you get from the File/Plot menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = HPGLOptions + """ [dict] Options for the `hpgl` output """ # pragma: no cover diff --git a/kiplot/out_ibom.py b/kiplot/out_ibom.py index 715ce322..040ba0b1 100644 --- a/kiplot/out_ibom.py +++ b/kiplot/out_ibom.py @@ -3,23 +3,16 @@ from subprocess import (check_output, STDOUT, CalledProcessError) from .misc import (CMD_IBOM, URL_IBOM, BOM_ERROR) from .gs import (GS) from .kiplot import (check_script) -from .optionable import Optionable +from .optionable import BaseOptions from kiplot.macros import macros, document, output_class # noqa: F401 from . import log logger = log.get_logger(__name__) -@output_class -class IBoM(BaseOutput): # noqa: F821 - """ IBoM (Interactive HTML BoM) - Generates an interactive web page useful to identify the position of the components in the PCB. - For more information: https://github.com/INTI-CMNB/InteractiveHtmlBom - This output is what you get from the InteractiveHtmlBom plug-in (pcbnew). """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._sch_related = True - # Options +class IBoMOptions(BaseOptions): + def __init__(self): + super().__init__() with document: self.dark_mode = False """ Default to dark mode """ @@ -85,7 +78,7 @@ class IBoM(BaseOutput): # noqa: F821 os.environ['INTERACTIVE_HTML_BOM_NO_DISPLAY'] = '' cmd = [CMD_IBOM, GS.pcb_file, '--dest-dir', output_dir, '--no-browser', ] # Convert attributes into options - for k, v in Optionable.get_attrs_gen(self): + for k, v in self.get_attrs_gen(): if not v: continue cmd.append(BaseOutput.attr2longopt(k)) # noqa: F821 @@ -101,3 +94,17 @@ class IBoM(BaseOutput): # noqa: F821 logger.debug('Output from command: '+e.output.decode()) exit(BOM_ERROR) logger.debug('Output from command:\n'+cmd_output.decode()+'\n') + + +@output_class +class IBoM(BaseOutput): # noqa: F821 + """ IBoM (Interactive HTML BoM) + Generates an interactive web page useful to identify the position of the components in the PCB. + For more information: https://github.com/INTI-CMNB/InteractiveHtmlBom + This output is what you get from the InteractiveHtmlBom plug-in (pcbnew). """ + def __init__(self): + super().__init__() + with document: + self.options = IBoMOptions + """ [dict] Options for the `ibom` output """ # pragma: no cover + self._sch_related = True diff --git a/kiplot/out_kibom.py b/kiplot/out_kibom.py index a015d3d6..b1edd0ee 100644 --- a/kiplot/out_kibom.py +++ b/kiplot/out_kibom.py @@ -4,22 +4,16 @@ from subprocess import (check_output, STDOUT, CalledProcessError) from .misc import (CMD_KIBOM, URL_KIBOM, BOM_ERROR) from .kiplot import (check_script) from .gs import (GS) +from .optionable import BaseOptions from kiplot.macros import macros, document, output_class # noqa: F401 from . import log logger = log.get_logger(__name__) -@output_class -class KiBoM(BaseOutput): # noqa: F821 - """ KiBoM (KiCad Bill of Materials) - Used to generate the BoM in HTML or CSV format using the KiBoM plug-in. - For more information: https://github.com/INTI-CMNB/KiBoM - This output is what you get from the 'Tools/Generate Bill of Materials' menu in eeschema. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._sch_related = True - # Options +class KiBoMOptions(BaseOptions): + def __init__(self): + super().__init__() with document: self.number = 1 """ Number of boards to build (components multiplier) """ @@ -62,3 +56,17 @@ class KiBoM(BaseOutput): # noqa: F821 for f in glob(os.path.join(output_dir, prj)+'*.tmp'): os.remove(f) logger.debug('Output from command:\n'+cmd_output.decode()) + + +@output_class +class KiBoM(BaseOutput): # noqa: F821 + """ KiBoM (KiCad Bill of Materials) + Used to generate the BoM in HTML or CSV format using the KiBoM plug-in. + For more information: https://github.com/INTI-CMNB/KiBoM + This output is what you get from the 'Tools/Generate Bill of Materials' menu in eeschema. """ + def __init__(self): + super().__init__() + with document: + self.options = KiBoMOptions + """ [dict] Options for the `kibom` output """ # pragma: no cover + self._sch_related = True diff --git a/kiplot/out_pdf.py b/kiplot/out_pdf.py index d4d7b161..0fb399f8 100644 --- a/kiplot/out_pdf.py +++ b/kiplot/out_pdf.py @@ -7,16 +7,9 @@ from . import log logger = log.get_logger(__name__) -@output_class -class PDF(AnyLayer, DrillMarks): - """ PDF (Portable Document Format) - Exports the PCB to the most common exhange format. Suitable for printing. - Note that this output isn't the best for documating your project. - This output is what you get from the File/Plot menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._plot_format = PLOT_FORMAT_PDF - # Options +class PDFOptions(DrillMarks): + def __init__(self): + super().__init__() with document: self.line_width = 0.1 """ [0.02,2] for objects without width [mm] """ @@ -24,21 +17,29 @@ class PDF(AnyLayer, DrillMarks): """ plot mirrored """ self.negative_plot = False """ invert black and white """ # pragma: no cover - - def config(self, outdir, options, layers): - AnyLayer.config(self, outdir, options, layers) - DrillMarks.config(self) + self._plot_format = PLOT_FORMAT_PDF def _configure_plot_ctrl(self, po, output_dir): - AnyLayer._configure_plot_ctrl(self, po, output_dir) - DrillMarks._configure_plot_ctrl(self, po, output_dir) + super()._configure_plot_ctrl(po, output_dir) po.SetMirror(self.mirror_plot) po.SetLineWidth(FromMM(self.line_width)) po.SetNegative(self.negative_plot) def read_vals_from_po(self, po): - AnyLayer.read_vals_from_po(self, po) - DrillMarks.read_vals_from_po(self, po) + super().read_vals_from_po(po) self.mirror_plot = po.GetMirror() self.line_width = ToMM(po.GetLineWidth()) self.negative_plot = po.GetNegative() + + +@output_class +class PDF(AnyLayer, DrillMarks): + """ PDF (Portable Document Format) + Exports the PCB to the most common exhange format. Suitable for printing. + Note that this output isn't the best for documating your project. + This output is what you get from the File/Plot menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = PDFOptions + """ [dict] Options for the `pdf` output """ # pragma: no cover diff --git a/kiplot/out_pdf_pcb_print.py b/kiplot/out_pdf_pcb_print.py index 55906584..535bdd98 100644 --- a/kiplot/out_pdf_pcb_print.py +++ b/kiplot/out_pdf_pcb_print.py @@ -5,39 +5,23 @@ from .error import (KiPlotConfigurationError) from .gs import (GS) from .kiplot import (check_script) from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT) +from .optionable import BaseOptions from kiplot.macros import macros, document, output_class # noqa: F401 +from .layer import Layer from . import log logger = log.get_logger(__name__) -@output_class -class PDF_Pcb_Print(BaseOutput): # noqa: F821 - """ PDF PCB Print (Portable Document Format) - Exports the PCB to the most common exhange format. Suitable for printing. - This is the main format to document your PCB. - This output is what you get from the 'File/Print' menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - # We need layers, so we define it - self._layers = None - # Options +class PDF_Pcb_PrintOptions(BaseOptions): + def __init__(self): + super().__init__() with document: self.output_name = '' """ filename for the output PDF (the name of the PCB if empty) """ # pragma: no cover - def config(self, outdir, options, layers): - super().config(outdir, options, layers) - # We need layers - if not self._layers: - raise KiPlotConfigurationError("Missing `layers` list") - - def run(self, output_dir, board): + def run(self, output_dir, board, layers): check_script(CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, '1.4.1') - # Verify the inner layers - layer_cnt = board.GetCopperLayerCount() - for l in self._layers: - l.get_layer_id_from_name(layer_cnt) # Output file name output = self.output_name if not output: @@ -51,10 +35,36 @@ class PDF_Pcb_Print(BaseOutput): # noqa: F821 cmd.insert(1, '-vv') cmd.insert(1, '-r') # Add the layers - for l in self._layers: - cmd.append(l.name) + layers = Layer.solve(layers) + cmd.extend([l.layer for l in layers]) + # Execute it logger.debug('Executing: '+str(cmd)) ret = call(cmd) if ret: logger.error(CMD_PCBNEW_PRINT_LAYERS+' returned %d', ret) exit(PDF_PCB_PRINT) + + +@output_class +class PDF_Pcb_Print(BaseOutput): # noqa: F821 + """ PDF PCB Print (Portable Document Format) + Exports the PCB to the most common exhange format. Suitable for printing. + This is the main format to document your PCB. + This output is what you get from the 'File/Print' menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = PDF_Pcb_PrintOptions + """ [dict] Options for the `pdf_pcb_print` output """ + self.layers = Layer + """ [list(dict)|list(string)|string] [all,selected,copper,technical,user] + List of PCB layers to include in the PDF """ # pragma: no cover + + def config(self, tree): + super().config(tree) + # We need layers + if isinstance(self.layers, type): + raise KiPlotConfigurationError("Missing `layers` list") + + def run(self, output_dir, board): + self.options.run(output_dir, board, self.layers) diff --git a/kiplot/out_pdf_sch_print.py b/kiplot/out_pdf_sch_print.py index 6796240a..b7d611f8 100644 --- a/kiplot/out_pdf_sch_print.py +++ b/kiplot/out_pdf_sch_print.py @@ -3,22 +3,16 @@ from subprocess import (call) from .gs import (GS) from .kiplot import (check_eeschema_do) from .misc import (CMD_EESCHEMA_DO, PDF_SCH_PRINT) +from .optionable import BaseOptions from kiplot.macros import macros, document, output_class # noqa: F401 from . import log logger = log.get_logger(__name__) -@output_class -class PDF_Sch_Print(BaseOutput): # noqa: F821 - """ PDF Schematic Print (Portable Document Format) - Exports the PCB to the most common exhange format. Suitable for printing. - This is the main format to document your schematic. - This output is what you get from the 'File/Print' menu in eeschema. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._sch_related = True - # Options +class PDF_Sch_PrintOptions(BaseOptions): + def __init__(self): + super().__init__() with document: self.output = '' """ filename for the output PDF (the name of the schematic if empty) """ # pragma: no cover @@ -39,3 +33,17 @@ class PDF_Sch_Print(BaseOutput): # noqa: F821 new = os.path.abspath(os.path.join(output_dir, self.output)) logger.debug('Moving '+cur+' -> '+new) os.rename(cur, new) + + +@output_class +class PDF_Sch_Print(BaseOutput): # noqa: F821 + """ PDF Schematic Print (Portable Document Format) + Exports the PCB to the most common exhange format. Suitable for printing. + This is the main format to document your schematic. + This output is what you get from the 'File/Print' menu in eeschema. """ + def __init__(self): + super().__init__() + with document: + self.options = PDF_Sch_PrintOptions + """ [dict] Options for the `pdf_sch_print` output """ # pragma: no cover + self._sch_related = True diff --git a/kiplot/out_position.py b/kiplot/out_position.py index 4964f319..5b4e95c9 100644 --- a/kiplot/out_position.py +++ b/kiplot/out_position.py @@ -2,17 +2,13 @@ import os import operator from datetime import datetime from pcbnew import (IU_PER_MM, IU_PER_MILS) +from .optionable import BaseOptions from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class Position(BaseOutput): # noqa: F821 - """ Pick & place - Generates the file with position information for the PCB components, used by the pick and place machine. - This output is what you get from the 'File/Fabrication output/Footprint poistion (.pos) file' menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - # Options +class PositionOptions(BaseOptions): + def __init__(self): + super().__init__() with document: self.format = 'ASCII' """ [ASCII,CSV] format for the position file """ @@ -153,3 +149,15 @@ class Position(BaseOutput): # noqa: F821 self._do_position_plot_ascii(board, output_dir, columns, modules, maxlengths) else: # if self.format == 'CSV': self._do_position_plot_csv(board, output_dir, columns, modules) + + +@output_class +class Position(BaseOutput): # noqa: F821 + """ Pick & place + Generates the file with position information for the PCB components, used by the pick and place machine. + This output is what you get from the 'File/Fabrication output/Footprint poistion (.pos) file' menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = PositionOptions + """ [dict] Options for the `position` output """ # pragma: no cover diff --git a/kiplot/out_ps.py b/kiplot/out_ps.py index 3cf831e0..50382e53 100644 --- a/kiplot/out_ps.py +++ b/kiplot/out_ps.py @@ -5,15 +5,9 @@ from kiplot.drill_marks import DrillMarks from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class PS(AnyLayer, DrillMarks): - """ PS (Postscript) - Exports the PCB to a format suitable for printing. - This output is what you get from the File/Plot menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._plot_format = PLOT_FORMAT_POST - # Options +class PSOptions(DrillMarks): + def __init__(self): + super().__init__() with document: self.line_width = 0.15 """ [0.02,2] for objects without width [mm] """ @@ -34,14 +28,10 @@ class PS(AnyLayer, DrillMarks): Only used to plot pads and tracks """ self.a4_output = True """ force A4 paper size """ # pragma: no cover - - def config(self, outdir, options, layers): - AnyLayer.config(self, outdir, options, layers) - DrillMarks.config(self) + self._plot_format = PLOT_FORMAT_POST def _configure_plot_ctrl(self, po, output_dir): - AnyLayer._configure_plot_ctrl(self, po, output_dir) - DrillMarks._configure_plot_ctrl(self, po, output_dir) + super()._configure_plot_ctrl(po, output_dir) po.SetWidthAdjust(self.width_adjust) po.SetFineScaleAdjustX(self.scale_adjust_x) po.SetFineScaleAdjustX(self.scale_adjust_y) @@ -59,8 +49,7 @@ class PS(AnyLayer, DrillMarks): po.SetScale(self.scaling) def read_vals_from_po(self, po): - AnyLayer.read_vals_from_po(self, po) - DrillMarks.read_vals_from_po(self, po) + super().read_vals_from_po(po) self.width_adjust = po.GetWidthAdjust() self.scale_adjust_x = po.GetFineScaleAdjustX() self.scale_adjust_y = po.GetFineScaleAdjustX() @@ -74,3 +63,15 @@ class PS(AnyLayer, DrillMarks): self.scaling = AUTO_SCALE else: self.scaling = po.GetScale() + + +@output_class +class PS(AnyLayer): + """ PS (Postscript) + Exports the PCB to a format suitable for printing. + This output is what you get from the File/Plot menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = PSOptions + """ [dict] Options for the `ps` output """ # pragma: no cover diff --git a/kiplot/out_step.py b/kiplot/out_step.py index 3026b60e..ba7b48de 100644 --- a/kiplot/out_step.py +++ b/kiplot/out_step.py @@ -4,21 +4,16 @@ from subprocess import (check_output, STDOUT, CalledProcessError) from .error import KiPlotConfigurationError from .misc import (KICAD2STEP, KICAD2STEP_ERR) from .gs import (GS) +from .optionable import BaseOptions from kiplot.macros import macros, document, output_class # noqa: F401 from . import log logger = log.get_logger(__name__) -@output_class -class STEP(BaseOutput): # noqa: F821 - """ STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure) - Exports the PCB as a 3D model. - This is the most common 3D format for exchange purposes. - This output is what you get from the 'File/Export/STEP' menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - # Options +class STEPOptions(BaseOptions): + def __init__(self): + super().__init__() with document: self.metric_units = True """ use metric units instead of inches. """ @@ -81,3 +76,16 @@ class STEP(BaseOutput): # noqa: F821 logger.debug('Output from command: '+e.output.decode()) exit(KICAD2STEP_ERR) logger.debug('Output from command:\n'+cmd_output.decode()) + + +@output_class +class STEP(BaseOutput): # noqa: F821 + """ STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure) + Exports the PCB as a 3D model. + This is the most common 3D format for exchange purposes. + This output is what you get from the 'File/Export/STEP' menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = STEPOptions + """ [dict] Options for the `step` output """ # pragma: no cover diff --git a/kiplot/out_svg.py b/kiplot/out_svg.py index 306cfc49..61882c4a 100644 --- a/kiplot/out_svg.py +++ b/kiplot/out_svg.py @@ -4,16 +4,9 @@ from kiplot.drill_marks import DrillMarks from kiplot.macros import macros, document, output_class # noqa: F401 -@output_class -class SVG(AnyLayer, DrillMarks): - """ SVG (Scalable Vector Graphics) - Exports the PCB to a format suitable for 2D graphics software. - Unlike bitmaps SVG drawings can be scaled without losing resolution. - This output is what you get from the File/Plot menu in pcbnew. """ - def __init__(self, name, type, description): - super().__init__(name, type, description) - self._plot_format = PLOT_FORMAT_SVG - # Options +class SVGOptions(DrillMarks): + def __init__(self): + super().__init__() with document: self.line_width = 0.25 """ [0.02,2] for objects without width [mm] """ @@ -21,21 +14,29 @@ class SVG(AnyLayer, DrillMarks): """ plot mirrored """ self.negative_plot = False """ invert black and white """ # pragma: no cover - - def config(self, outdir, options, layers): - AnyLayer.config(self, outdir, options, layers) - DrillMarks.config(self) + self._plot_format = PLOT_FORMAT_SVG def _configure_plot_ctrl(self, po, output_dir): - AnyLayer._configure_plot_ctrl(self, po, output_dir) - DrillMarks._configure_plot_ctrl(self, po, output_dir) + super()._configure_plot_ctrl(po, output_dir) po.SetMirror(self.mirror_plot) po.SetLineWidth(FromMM(self.line_width)) po.SetNegative(self.negative_plot) def read_vals_from_po(self, po): - AnyLayer.read_vals_from_po(self, po) - DrillMarks.read_vals_from_po(self, po) + super().read_vals_from_po(po) self.line_width = ToMM(po.GetLineWidth()) self.negative_plot = po.GetNegative() self.mirror_plot = po.GetMirror() + + +@output_class +class SVG(AnyLayer): + """ SVG (Scalable Vector Graphics) + Exports the PCB to a format suitable for 2D graphics software. + Unlike bitmaps SVG drawings can be scaled without losing resolution. + This output is what you get from the File/Plot menu in pcbnew. """ + def __init__(self): + super().__init__() + with document: + self.options = SVGOptions + """ [dict] Options for the `svg` output """ # pragma: no cover diff --git a/kiplot/reg_out.py b/kiplot/reg_out.py new file mode 100644 index 00000000..43b71518 --- /dev/null +++ b/kiplot/reg_out.py @@ -0,0 +1,25 @@ +from .optionable import Optionable + + +class RegOutput(Optionable): + """ This class adds the mechanism to register outputs """ + _registered = {} + + def __init__(self): + super().__init__() + + @staticmethod + def register(name, aclass): + RegOutput._registered[name] = aclass + + @staticmethod + def is_registered(name): + return name in RegOutput._registered + + @staticmethod + def get_class_for(name): + return RegOutput._registered[name] + + @staticmethod + def get_registered(): + return RegOutput._registered diff --git a/tests/board_samples/good-project.kicad_pcb b/tests/board_samples/good-project.kicad_pcb index 01baad5f..4c75be3f 100644 --- a/tests/board_samples/good-project.kicad_pcb +++ b/tests/board_samples/good-project.kicad_pcb @@ -79,7 +79,7 @@ (useauxorigin false) (hpglpennumber 1) (hpglpenspeed 20) - (hpglpendiameter 15.000000) + (hpglpendiameter 35.000000) (psnegative false) (psa4output false) (plotreference true) diff --git a/tests/test_plot/test_misc.py b/tests/test_plot/test_misc.py index ff41a358..608ff14a 100644 --- a/tests/test_plot/test_misc.py +++ b/tests/test_plot/test_misc.py @@ -343,7 +343,6 @@ def test_example_1(): ctx = context.TestContext('Example1', '3Rs', 'pre_and_position', '') ctx.run(extra=['--example'], no_verbose=True, no_yaml_file=True, no_board_file=True) assert ctx.expect_out_file(EXAMPLE_CFG) - os.remove(ctx.get_out_path(EXAMPLE_CFG)) ctx.clean_up() @@ -351,8 +350,7 @@ def test_example_2(): ctx = context.TestContext('Example2', 'good-project', 'pre_and_position', '') ctx.run(extra=['--example'], no_verbose=True, no_yaml_file=True) assert ctx.expect_out_file(EXAMPLE_CFG) - ctx.search_in_file(EXAMPLE_CFG, ['GND.Cu']) - os.remove(ctx.get_out_path(EXAMPLE_CFG)) + ctx.search_in_file(EXAMPLE_CFG, ['layers: all']) ctx.clean_up() @@ -361,15 +359,21 @@ def test_example_3(): ctx.run(extra=['--example'], no_verbose=True, no_yaml_file=True) assert ctx.expect_out_file(EXAMPLE_CFG) ctx.run(WONT_OVERWRITE, extra=['--example'], no_verbose=True, no_yaml_file=True) - os.remove(ctx.get_out_path(EXAMPLE_CFG)) ctx.clean_up() def test_example_4(): ctx = context.TestContext('Example4', 'good-project', 'pre_and_position', '') + ctx.run(extra=['--example', '-P'], no_verbose=True, no_yaml_file=True) + assert ctx.expect_out_file(EXAMPLE_CFG) + ctx.search_in_file(EXAMPLE_CFG, ['GND.Cu', 'pen_width: 35.0']) + ctx.search_not_in_file(EXAMPLE_CFG, ['F.Adhes']) + ctx.clean_up() + + +def test_example_5(): + ctx = context.TestContext('Example5', 'good-project', 'pre_and_position', '') ctx.run(extra=['--example', '-p'], no_verbose=True, no_yaml_file=True) assert ctx.expect_out_file(EXAMPLE_CFG) - ctx.search_in_file(EXAMPLE_CFG, ['GND.Cu']) - ctx.search_not_in_file(EXAMPLE_CFG, ['F.Adhes']) - os.remove(ctx.get_out_path(EXAMPLE_CFG)) + ctx.search_in_file(EXAMPLE_CFG, ['layers: selected', 'pen_width: 35.0']) ctx.clean_up() diff --git a/tests/test_plot/test_svg.py b/tests/test_plot/test_svg.py index 3541475b..955cec81 100644 --- a/tests/test_plot/test_svg.py +++ b/tests/test_plot/test_svg.py @@ -30,3 +30,200 @@ def test_svg(): ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) ctx.clean_up() + + +def test_svg_all(): + prj = 'simple_2layer' + ctx = context.TestContext('SVGAll', prj, 'svg_all', PS_DIR) + ctx.run() + + ctx.expect_out_file(ctx.get_gerber_filename('B_Adhes', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_CrtYd', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Fab', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Mask', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Paste', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_SilkS', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Cmts_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Dwgs_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Eco1_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Eco2_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Edge_Cuts', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Adhes', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_CrtYd', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Fab', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Mask', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Paste', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_SilkS', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Margin', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) + + ctx.clean_up() + + +def test_svg_selected(): + prj = 'simple_2layer' + ctx = context.TestContext('SVGSelected', prj, 'svg_selected', PS_DIR) + ctx.run() + + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_CrtYd', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Cu', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Fab', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Mask', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_Paste', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('B_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Cmts_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Dwgs_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco1_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco2_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Edge_Cuts', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_CrtYd', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Cu', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Fab', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Mask', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Paste', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Margin', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) + + ctx.clean_up() + + +def test_svg_copper_and_user(): + prj = 'good-project' + ctx = context.TestContext('SVGCopperUser', prj, 'svg_copper_and_user', PS_DIR) + ctx.run() + + ctx.expect_out_file(ctx.get_gerber_filename('B_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Cmts_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Dwgs_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Eco1_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Eco2_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Edge_Cuts', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('GND_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Margin', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Power_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal1_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal2_Cu', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) + + ctx.clean_up() + + +def test_svg_copper_and_draw(): + prj = 'good-project' + ctx = context.TestContext('SVGCopperDraw', prj, 'svg_copper_and_draw', PS_DIR) + ctx.run() + + ctx.expect_out_file(ctx.get_gerber_filename('B_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Dwgs_User', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('GND_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Power_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal1_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal2_Cu', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Margin', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco1_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco2_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Edge_Cuts', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Cmts_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) + + ctx.clean_up() + + +def test_svg_copper_and_cmt(): + prj = 'good-project' + ctx = context.TestContext('SVGCopperCmt', prj, 'svg_copper_and_cmt', PS_DIR) + ctx.run() + + ctx.expect_out_file(ctx.get_gerber_filename('B_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('GND_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Power_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal1_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal2_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Cmts_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Dwgs_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Margin', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco1_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco2_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Edge_Cuts', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) + + ctx.clean_up() + + +def test_svg_anchor(): + prj = 'good-project' + ctx = context.TestContext('SVGCopperCmt', prj, 'svg_anchor', PS_DIR) + ctx.run(extra=['SVG']) + + assert ctx.search_err(r"- 'SVG files' \(SVG\) \[svg\]") + ctx.expect_out_file(ctx.get_gerber_filename('B_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('F_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('GND_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Power_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal1_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Signal2_Cu', '.svg')) + ctx.expect_out_file(ctx.get_gerber_filename('Cmts_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Dwgs_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Margin', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco1_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Eco2_User', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('Edge_Cuts', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('B_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Adhes', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_CrtYd', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Fab', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Mask', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_Paste', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_filename('F_SilkS', '.svg')) + ctx.dont_expect_out_file(ctx.get_gerber_job_filename()) + + ctx.clean_up() diff --git a/tests/test_plot/test_yaml_errors.py b/tests/test_plot/test_yaml_errors.py index 83a7370a..32e91dac 100644 --- a/tests/test_plot/test_yaml_errors.py +++ b/tests/test_plot/test_yaml_errors.py @@ -150,8 +150,8 @@ def test_drill_report_wrong_type_3(): def test_wrong_layer_1(): ctx = context.TestContext('ErrorWrongLayer1', '3Rs', 'error_wrong_layer_1', None) - ctx.run(PLOT_ERROR) - assert ctx.search_err("Unknown layer name: F.Bogus") + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err("Unknown layer name: .?F.Bogus.?") ctx.clean_up() @@ -164,29 +164,45 @@ def test_wrong_layer_2(): def test_wrong_layer_3(): ctx = context.TestContext('ErrorWrongLayer3', '3Rs', 'error_wrong_layer_3', None) - ctx.run(PLOT_ERROR) - assert ctx.search_err("Malformed inner layer name: Inner_1,") + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err("Malformed inner layer name: .?Inner_1.?,") ctx.clean_up() def test_wrong_layer_4(): ctx = context.TestContext('ErrorWrongLayer4', '3Rs', 'error_wrong_layer_4', None) ctx.run(EXIT_BAD_CONFIG) - assert ctx.search_err(".?layers.? must be a list") + assert ctx.search_err(".?layers.? must be any of") ctx.clean_up() def test_wrong_layer_5(): ctx = context.TestContext('ErrorWrongLayer5', '3Rs', 'error_wrong_layer_5', None) ctx.run(EXIT_BAD_CONFIG) - assert ctx.search_err("Unknown .?bogus.? attribute ") + assert ctx.search_err("Unknown option .?bogus.?") ctx.clean_up() def test_wrong_layer_6(): ctx = context.TestContext('ErrorWrongLayer6', '3Rs', 'error_wrong_layer_6', None) ctx.run(EXIT_BAD_CONFIG) - assert ctx.search_err("Missing .?layer.? attribute") + assert ctx.search_err("Missing or empty .?layer.? attribute") + ctx.clean_up() + + +def test_wrong_layer_7(): + """ List of numbers """ + ctx = context.TestContext('ErrorWrongLayer7', '3Rs', 'error_wrong_layer_7', None) + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err(".?layers.? must be any of") + ctx.clean_up() + + +def test_wrong_layer_8(): + """ List of strings, but number in middle """ + ctx = context.TestContext('ErrorWrongLayer8', '3Rs', 'error_wrong_layer_8', None) + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err(".?4.? must be any of") ctx.clean_up() @@ -207,7 +223,14 @@ def test_no_type(): def test_out_unknown_attr(): ctx = context.TestContext('ErrorUnkOutAttr', '3Rs', 'error_unk_attr', None) ctx.run(EXIT_BAD_CONFIG) - assert ctx.search_err("Unknown key .?types.?") + assert ctx.search_err("Unknown option .?directory.?") + ctx.clean_up() + + +def test_out_needs_type(): + ctx = context.TestContext('ErrorNeedsType', '3Rs', 'error_needs_type', None) + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err("needs a type") ctx.clean_up() diff --git a/tests/yaml_samples/error_needs_type.kiplot.yaml b/tests/yaml_samples/error_needs_type.kiplot.yaml new file mode 100644 index 00000000..6594ea6c --- /dev/null +++ b/tests/yaml_samples/error_needs_type.kiplot.yaml @@ -0,0 +1,28 @@ +kiplot: + version: 1 + +outputs: + - name: PDF + comment: "PDF files" + types: pdf + dir: PDF + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # PDF options + drill_marks: small + mirror_plot: false + negative_plot: false + line_width: 0.01 + layers: + - layer: F.Cu + suffix: F_Cu + - layer: F.Bogus + suffix: B_Silks diff --git a/tests/yaml_samples/error_unk_attr.kiplot.yaml b/tests/yaml_samples/error_unk_attr.kiplot.yaml index 6594ea6c..013aca8d 100644 --- a/tests/yaml_samples/error_unk_attr.kiplot.yaml +++ b/tests/yaml_samples/error_unk_attr.kiplot.yaml @@ -4,8 +4,8 @@ kiplot: outputs: - name: PDF comment: "PDF files" - types: pdf - dir: PDF + type: pdf + directory: PDF options: exclude_edge_layer: false exclude_pads_from_silkscreen: false diff --git a/tests/yaml_samples/error_wrong_layer_7.kiplot.yaml b/tests/yaml_samples/error_wrong_layer_7.kiplot.yaml new file mode 100644 index 00000000..25324552 --- /dev/null +++ b/tests/yaml_samples/error_wrong_layer_7.kiplot.yaml @@ -0,0 +1,27 @@ +kiplot: + version: 1 + +outputs: + - name: PDF + comment: "PDF files" + type: pdf + dir: PDF + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # PDF options + drill_marks: small + mirror_plot: false + negative_plot: false + line_width: 0.01 + layers: + - 1 + - 2 + diff --git a/tests/yaml_samples/error_wrong_layer_8.kiplot.yaml b/tests/yaml_samples/error_wrong_layer_8.kiplot.yaml new file mode 100644 index 00000000..7450f4f5 --- /dev/null +++ b/tests/yaml_samples/error_wrong_layer_8.kiplot.yaml @@ -0,0 +1,28 @@ +kiplot: + version: 1 + +outputs: + - name: PDF + comment: "PDF files" + type: pdf + dir: PDF + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # PDF options + drill_marks: small + mirror_plot: false + negative_plot: false + line_width: 0.01 + layers: + - all + - 4 + - user + diff --git a/tests/yaml_samples/svg_all.kiplot.yaml b/tests/yaml_samples/svg_all.kiplot.yaml new file mode 100644 index 00000000..7aa4e584 --- /dev/null +++ b/tests/yaml_samples/svg_all.kiplot.yaml @@ -0,0 +1,24 @@ +kiplot: + version: 1 + +outputs: + - name: SVG + comment: "SVG files" + type: svg + dir: SVG + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # SVG options + line_width: 0.25 + drill_marks: full + mirror_plot: true + negative_plot: true + layers: all diff --git a/tests/yaml_samples/svg_anchor.kiplot.yaml b/tests/yaml_samples/svg_anchor.kiplot.yaml new file mode 100644 index 00000000..70002e73 --- /dev/null +++ b/tests/yaml_samples/svg_anchor.kiplot.yaml @@ -0,0 +1,33 @@ +kiplot: + version: 1 + +outputs: + - name: postscript + comment: "Postscript files" + type: ps + dir: PS + layers: &copper_and_cmts + - copper + - 'Cmts.User' + + - name: SVG + comment: "SVG files" + type: svg + dir: SVG + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # SVG options + line_width: 0.25 + drill_marks: full + mirror_plot: true + negative_plot: true + layers: *copper_and_cmts + diff --git a/tests/yaml_samples/svg_copper_and_cmt.kiplot.yaml b/tests/yaml_samples/svg_copper_and_cmt.kiplot.yaml new file mode 100644 index 00000000..7800b3d9 --- /dev/null +++ b/tests/yaml_samples/svg_copper_and_cmt.kiplot.yaml @@ -0,0 +1,27 @@ +kiplot: + version: 1 + +outputs: + - name: SVG + comment: "SVG files" + type: svg + dir: SVG + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # SVG options + line_width: 0.25 + drill_marks: full + mirror_plot: true + negative_plot: true + layers: + - copper + - 'Cmts.User' + diff --git a/tests/yaml_samples/svg_copper_and_draw.kiplot.yaml b/tests/yaml_samples/svg_copper_and_draw.kiplot.yaml new file mode 100644 index 00000000..72604a35 --- /dev/null +++ b/tests/yaml_samples/svg_copper_and_draw.kiplot.yaml @@ -0,0 +1,29 @@ +kiplot: + version: 1 + +outputs: + - name: SVG + comment: "SVG files" + type: svg + dir: SVG + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # SVG options + line_width: 0.25 + drill_marks: full + mirror_plot: true + negative_plot: true + layers: + - copper + - layer: 'Dwgs.User' + suffix: 'Dwgs_User' + description: 'User drawings' + diff --git a/tests/yaml_samples/svg_copper_and_user.kiplot.yaml b/tests/yaml_samples/svg_copper_and_user.kiplot.yaml new file mode 100644 index 00000000..e684abe1 --- /dev/null +++ b/tests/yaml_samples/svg_copper_and_user.kiplot.yaml @@ -0,0 +1,26 @@ +kiplot: + version: 1 + +outputs: + - name: SVG + comment: "SVG files" + type: svg + dir: SVG + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # SVG options + line_width: 0.25 + drill_marks: full + mirror_plot: true + negative_plot: true + layers: + - copper + - user diff --git a/tests/yaml_samples/svg_selected.kiplot.yaml b/tests/yaml_samples/svg_selected.kiplot.yaml new file mode 100644 index 00000000..dc2efd77 --- /dev/null +++ b/tests/yaml_samples/svg_selected.kiplot.yaml @@ -0,0 +1,24 @@ +kiplot: + version: 1 + +outputs: + - name: SVG + comment: "SVG files" + type: svg + dir: SVG + options: + exclude_edge_layer: false + exclude_pads_from_silkscreen: false + use_aux_axis_as_origin: false + plot_sheet_reference: false + plot_footprint_refs: true + plot_footprint_values: true + force_plot_invisible_refs_vals: false + tent_vias: true + + # SVG options + line_width: 0.25 + drill_marks: full + mirror_plot: true + negative_plot: true + layers: selected