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.
This commit is contained in:
Salvador E. Tropea 2020-07-08 15:17:24 -03:00
parent 1c4c94cfa2
commit 4368364489
45 changed files with 1631 additions and 1149 deletions

View File

@ -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.

View File

@ -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

527
README.md
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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()]

View File

@ -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)

View File

@ -14,6 +14,7 @@ class GS(object):
sch_file = None
out_dir = None
filter_file = None
board = None
debug_enabled = False
@staticmethod

View File

@ -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))

211
kiplot/layer.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

25
kiplot/reg_out.py Normal file
View File

@ -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

View File

@ -79,7 +79,7 @@
(useauxorigin false)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(hpglpendiameter 35.000000)
(psnegative false)
(psa4output false)
(plotreference true)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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