From 99467d4d050eadf612d5137a529b13196586adbc Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 28 Nov 2022 16:31:00 -0300 Subject: [PATCH] [Added] Panelize output - First working version --- CHANGELOG.md | 1 + README.md | 297 ++++++++- docs/README.in | 2 +- docs/samples/generic_plot.kibot.yaml | 414 ++++++++++++ kibot/misc.py | 1 + kibot/out_panelize.py | 627 ++++++++++++++++++ .../panelize/panel_4x4.kibot.yaml | 62 ++ src/kibot-check | 33 + tests/yaml_samples/panelize_1.kibot.yaml | 61 ++ 9 files changed, 1496 insertions(+), 2 deletions(-) create mode 100644 kibot/out_panelize.py create mode 100644 kibot/resources/config_templates/panelize/panel_4x4.kibot.yaml create mode 100644 tests/yaml_samples/panelize_1.kibot.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index ba1e1ebd..6986ae31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New output: - `populate` to create step-by-step assembly instructions With support for `pcbdraw` and `render_3d`. + - `panelize` to create a PCB panel containing N copies of the PCB. - generic filters: options to filter by PCB side - BoM: - Option to link to Mouser site. diff --git a/README.md b/README.md index 1cae87f3..c3d4f9f5 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,9 @@ Notes: [**KiCad PCB/SCH Diff**](https://github.com/INTI-CMNB/KiDiff) v2.4.3 [![Tool](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png)](https://github.com/INTI-CMNB/KiDiff) ![Auto-download](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/auto_download-22x22.png) - Mandatory for `diff` +[**KiKit**](https://github.com/yaqwsx/KiKit) [![Tool](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png)](https://github.com/yaqwsx/KiKit) ![Auto-download](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/auto_download-22x22.png) +- Mandatory for `panelize` + [**mistune**](https://pypi.org/project/mistune/) [![Python module](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png)](https://pypi.org/project/mistune/) [![Debian](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png)](https://packages.debian.org/bullseye/python3-mistune) - Mandatory for `populate` @@ -2443,6 +2446,298 @@ Notes: Internally we use 10 for low priority, 90 for high priority and 50 for most outputs. - `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested. +* Panelize + * Type: `panelize` + * Description: Creates a panel to fabricate various copies of the PCB at once. + It currently uses the KiKit tool, which must be available. + Consult KiKit docs for detailed information. + Note that you don't need to specify the units for all distances. + If they are omitted they are assumed to be `default_units`. + The same is valid for angles, using `default_angles` + * Valid keys: + - **`comment`**: [string=''] A comment for documentation purposes. + - **`dir`**: [string='./'] Output directory for the generated files. + If it starts with `+` the rest is concatenated to the default dir. + - **`name`**: [string=''] Used to identify this particular output definition. + - **`options`**: [dict] Options for the `Panelize` output. + * Valid keys: + - **`configs`**: [list(dict)|list(string)|string] One or more configurations used to create the panel. + Use a string to include an external configuration, i.e. `myDefault.json`. + You can also include a preset using `:name`, i.e. `:vcuts`. + Use a dict to specify the options using the KiBot YAML file. + * Valid keys: + - **`cuts`**: [dict] Specify how to perform the cuts on the tabs separating the board. + * Valid keys: + - `clearance`: [number|string] Specify clearance for copper around V-cuts. + - *cut_curves*: Alias for cutcurves. + - `cutcurves`: [boolean=false] Specify if curves should be approximated by straight cuts (e.g., for cutting tabs on circular boards). + Used for *vcuts*. + - `drill`: [number|string] Drill size used for the *mousebites*. + - `layer`: [string='Cmts.User'] Specify the layer to render V-cuts on. Also used for the *layer* type. + - `offset`: [number|string] Specify the *mousebites* and *vcuts* offset, positive offset puts the cuts into the board, + negative puts the cuts into the tabs. + - `prolong`: [number|string] Distance for tangential prolongation of the cuts (to cut through the internal corner fillets + caused by milling). Used for *mousebites* and *layer*. + - `spacing`: [number|string] The spacing of the holes used for the *mousebites*. + - `type`: [string='none'] [none,mousebites,vcuts,layer] Layer: When KiKit reports it cannot perform cuts, you can render the cuts + into a layer with this option to understand what's going on. Shouldn't be used for the final design. + - **`fiducials`**: [dict] Used to add fiducial marks to the (rail/frame of) the panel. + * Valid keys: + - *copper_size*: Alias for coppersize. + - `coppersize`: [number|string] Diameter of the copper spot. + - `hoffset`: [number|string] Horizontal offset from panel edges. + - `opening`: [number|string] Diameter of the solder mask opening. + - `type`: [string='none'] [none,3fid,4fid] Add none, 3 or 4 fiducials to the (rail/frame of) the panel. + - `voffset`: [number|string] Vertical offset from panel edges. + - **`framing`**: [dict] Specify the frame around the boards. + * Valid keys: + - `chamfer`: [number|string] Specify the size of chamfer frame corners. + - `cuts`: [string='both'] [none,both,v,h] Specify whether to add cuts to the corners of the frame for easy removal. + Used for *frame*. + - `fillet`: [number|string] Specify radius of fillet frame corners. + - `hspace`: [number|string] Specify the horizontal space between PCB and the frame/rail. + - *min_total_height*: Alias for mintotalheight. + - *min_total_width*: Alias for mintotalwidth. + - `mintotalheight`: [number|string] If needed, add extra material to the rail or frame to meet the minimal requested size. + Useful for services that require minimal panel size. + - `mintotalwidth`: [number|string] If needed, add extra material to the rail or frame to meet the minimal requested size. + Useful for services that require minimal panel size. + - *slot_width*: Alias for slotwidth. + - `slotwidth`: [number|string] Width of the milled slot for *tightframe*. + - `space`: [number|string] Specify the space between PCB and the frame/rail. Overrides `hspace` and `vspace`. + - `type`: [string='none'] [none,railstb,railslr,frame,tightframe] Railstb: Add rails on top and bottom. + Railslr: Add rails on left and right. + Frame: Add a frame around the board. + Tighframe: Add a frame around the board which fills the whole area of the panel - + the boards have just a milled slot around their perimeter. + - `vspace`: [number|string] Specify the vertical space between PCB and the frame/rail. + - `width`: [number|string] Specify with of the rails or frame. + - **`layout`**: [dict] Layout used for the panel. + * Valid keys: + - **`cols`**: [number=1] Specify the number of columns of boards in the grid pattern. + - **`rows`**: [number=1] Specify the number of rows of boards in the grid pattern. + - `alternation`: [string='none'] [none,rows,cols,rowsCols] Specify alternations of board rotation. + none: Do not alternate. + rows: Rotate boards by 180° on every next row. + cols: Rotate boards by 180° on every next column. + rowsCols: Rotate boards by 180° based on a chessboard pattern. + - *bake_text*: Alias for baketext. + - `baketext`: [boolean=true] A flag that indicates if text variables should be substituted or not. + - `hbackbone`: [number|string] The width of horizontal backbone (0 means no backbone). The backbone does not increase the + spacing of the boards. + - `hbonecut`: [boolean=true] If there are both backbones specified, specifies if there should be a horizontal cut where the backbones + cross. + - `hboneskip`: [number=0] Skip every n horizontal backbones. I.e., 1 means place only every other backbone. + - `hspace`: [number|string] Specify the horizontal gap between the boards. + - *rename_net*: Alias for renamenet. + - *rename_ref*: Alias for renameref. + - `renamenet`: [string='Board_{n}-{orig}'] A pattern by which to rename the nets. You can use {n} and {orig} to get the board number and original name. + - `renameref`: [string='{orig}'] A pattern by which to rename the references. You can use {n} and {orig} to get the board number and original + name. + - `rotation`: [number|string] Rotate the boards before placing them in the panel. + - `space`: [number|string] Specify the gap between the boards, overwrites `hspace` and `vspace`. + - `type`: [string='grid'] [grid] Currently fixed. + - `vbackbone`: [number|string] The width of vertical backbone (0 means no backbone). The backbone does not increase the + spacing of the boards. + - `vbonecut`: [boolean=true] If there are both backbones specified, specifies if there should be a vertical cut where the backbones + cross. + - `vboneskip`: [number=0] Skip every n vertical backbones. I.e., 1 means place only every other backbone. + - `vspace`: [number|string] Specify the vertical gap between the boards. + - **`page`**: [dict] Sets page size on the resulting panel and position the panel in the page. + * Valid keys: + - *page_size*: Alias for type. + - *size*: Alias for type. + - **`type`**: [string='inherit'] [inherit,custom,A0,A1,A2,A3,A4,A5,A,B,C,D,E,USLetter,USLegal,USLedger,A0-portrait,A1-portrait,A2-portrait, + A3-portrait,A4-portrait,A5-portrait,A-portrait,B-portrait,C-portrait,D-portrait,E-portrait, + USLetter-portrait,USLegal-portrait,USLedger-portrait] Paper size. The default `inherit` option inherits + paper size from the source board. This feature is not supported on KiCAD 5. + - `anchor`: [string='tl'] [tl,tr,bl,br,mt,mb,ml,mr,c] Point of the panel to be placed at given position. Can be one of tl, tr, bl, br + (corners), mt, mb, ml, mr (middle of sides), c (center). The anchors refer to the panel outline. + - `height`: [number|string] Height for the `custom` paper size. + - *pos_x*: Alias for posx. + - *pos_y*: Alias for posy. + - `posx`: [number|string] The X position of the panel on the page. + - `posy`: [number|string] The Y position of the panel on the page. + - `width`: [number|string] Width for the `custom` paper size. + - **`tabs`**: [dict] Style of the tabs used to join the PCB copies. + * Valid keys: + - `cutout`: [number|string] When your design features open pockets on the side, this parameter specifies extra cutout depth in order to + ensure that a sharp corner of the pocket can be milled. Used for *full*. + - `hcount`: [number=1] Number of tabs in the horizontal direction. Used for *fixed*. + - `hwidth`: [number|string] The width of tabs in the horizontal direction. Used for *fixed* and *spacing*. + - *min_distance*: Alias for mindistance. + - `mindistance`: [number|string] Minimal spacing between the tabs. If there are too many tabs, their count is reduced. + Used for *fixed*. + - `spacing`: [number|string] The maximum spacing of the tabs. Used for *spacing*. + - *tab_footprints*: Alias for tabfootprints. + - `tabfootprints`: [string='kikit:Tab'] The footprint/s used for the *annotation* type. You can specify a list of footprints separated by comma. + - `type`: [string='spacing'] [fixed,spacing,full,annotation] Fixed: Place given number of tabs on the PCB edge. + Spacing: Place tabs on the PCB edges based on spacing. + Full: Create tabs that are full width of the PCB. + Corner: Create tabs in the corners of the PCB. + Annotation: Add tabs based on PCB annotations. + - `vcount`: [number=1] Number of tabs in the vertical direction. Used for *fixed*. + - `vwidth`: [number|string] The width of tabs in the vertical direction. Used for *fixed* and *spacing*. + - `width`: [number|string] The width of tabs in both directions. Overrides both `vwidth` and `hwidth`. + Used for *fixed*, *spacing*, *corner* and *annotation*. + - **`tooling`**: [dict] Used to add tooling holes to the (rail/frame of) the panel. + * Valid keys: + - `hoffset`: [number|string] Horizontal offset from panel edges. + - `paste`: [boolean=false] If True, the holes are included in the paste layer (therefore they appear on the stencil). + - `size`: [number|string] Diameter of the holes. + - `type`: [string='none'] [none,3hole,4hole] Add none, 3 or 4 holes to the (rail/frame of) the panel. + - `voffset`: [number|string] Vertical offset from panel edges. + - `copperfill`: [dict] Fill non-board areas of the panel with copper. + * Valid keys: + - `clearance`: [number|string] Extra clearance from the board perimeters. Suitable for, e.g., not filling the tabs with + copper. + - `layers`: [string|list(string)] List of layers to fill. Can be a comma-separated string. + Using *all* means all external copper layers. + - `orientation`: [number|string] The orientation of the hatched strokes. + - `spacing`: [number|string] The space between the hatched strokes. + - `type`: [string='none'] [none,solid,hatched] How to fill non-board areas of the panel with copper. + - `width`: [number|string] The width of the hatched strokes. + - `debug`: [dict] Debug options. + * Valid keys: + - `deterministic`: [boolean=false] Deterministic. + - `drawBackboneLines`: [boolean=false] Draw backbone lines. + - `drawPartitionLines`: [boolean=false] Draw partition lines. + - `drawboxes`: [boolean=false] Draw boxes. + - `drawtabfail`: [boolean=false] Draw tab fail. + - `trace`: [boolean=false] Trace. + - `post`: [dict] Finishing touches to the panel. + * Valid keys: + - `copperfill`: [boolean=false] Fill tabs and frame with copper (e.g., to save etchant or to increase rigidity of flex-PCB panels). + - *mill_radius*: Alias for millradius. + - `millradius`: [number|string] Simulate the milling operation (add fillets to the internal corners). + Specify mill radius (usually 1 mm). 0 radius disables the functionality. + - `origin`: [string='tl'] [tl,tr,bl,br,mt,mb,ml,mr,c] Specify if the auxiliary origin an grid origin should be placed. + Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr (middle of sides), c (center). + Empty string does not changes the origin. + - *reconstruct_arcs*: Alias for reconstructarcs. + - `reconstructarcs`: [boolean=false] The panelization process works on top of a polygonal representation of the board. + This options allows to reconstruct the arcs in the design before saving the panel. + - *refill_zones*: Alias for refillzones. + - `refillzones`: [boolean=false] Refill the user zones after the panel is build. + This is only necessary when you want your zones to avoid cuts in panel. + - `script`: [string=''] A path to custom Python file. The file should contain a function kikitPostprocess(panel, args) that + receives the prepared panel as the kikit.panelize.Panel object and the user-supplied arguments as a + string - see `scriptarg`. The function can make arbitrary changes to the panel - you can append text, + footprints, alter labels, etc. The function is invoked after the whole panel is constructed + (including all other postprocessing). If you try to add a functionality for a common fabrication + houses via scripting, consider submitting PR for KiKit. + - *script_arg*: Alias for scriptarg. + - `scriptarg`: [string=''] An arbitrary string passed to the user post-processing script specified in script. + - `type`: [string='auto'] [auto] Currently fixed. + - `text`: [dict] Used to add text to the panel. + * Valid keys: + - `anchor`: [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + (middle of sides), c (center). The anchors refer to the panel outline. + - `height`: [number|string] Height of the characters (the same parameters as KiCAD uses). + - `hjustify`: [string='center'] [left,right,center] Horizontal justification of the text. + - `hoffset`: [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system. + - `layer`: [string='F.SilkS'] Specify text layer. + - `orientation`: [number|string] Specify the orientation (angle). + - `text`: [string=''] The text to be displayed. Note that you can escape ; via \. + Available variables in text: *date* formats current date as --, + *time24* formats current time in 24-hour format, + *boardTitle* the title from the source board, + *boardDate* the date from the source board, + *boardRevision* the revision from the source board, + *boardCompany* the company from the source board, + *boardComment1*-*boardComment9* comments from the source board. + - `thickness`: [number|string] Stroke thickness. + - `type`: [string='simple'] [simple] Currently fixed. + - `vjustify`: [string='center'] [left,right,center] Vertical justification of the text. + - `voffset`: [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system. + - `width`: [number|string] Width of the characters (the same parameters as KiCAD uses). + - `text2`: [dict] Used to add text to the panel. + * Valid keys: + - `anchor`: [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + (middle of sides), c (center). The anchors refer to the panel outline. + - `height`: [number|string] Height of the characters (the same parameters as KiCAD uses). + - `hjustify`: [string='center'] [left,right,center] Horizontal justification of the text. + - `hoffset`: [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system. + - `layer`: [string='F.SilkS'] Specify text layer. + - `orientation`: [number|string] Specify the orientation (angle). + - `text`: [string=''] The text to be displayed. Note that you can escape ; via \. + Available variables in text: *date* formats current date as --, + *time24* formats current time in 24-hour format, + *boardTitle* the title from the source board, + *boardDate* the date from the source board, + *boardRevision* the revision from the source board, + *boardCompany* the company from the source board, + *boardComment1*-*boardComment9* comments from the source board. + - `thickness`: [number|string] Stroke thickness. + - `type`: [string='simple'] [simple] Currently fixed. + - `vjustify`: [string='center'] [left,right,center] Vertical justification of the text. + - `voffset`: [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system. + - `width`: [number|string] Width of the characters (the same parameters as KiCAD uses). + - `text3`: [dict] Used to add text to the panel. + * Valid keys: + - `anchor`: [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + (middle of sides), c (center). The anchors refer to the panel outline. + - `height`: [number|string] Height of the characters (the same parameters as KiCAD uses). + - `hjustify`: [string='center'] [left,right,center] Horizontal justification of the text. + - `hoffset`: [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system. + - `layer`: [string='F.SilkS'] Specify text layer. + - `orientation`: [number|string] Specify the orientation (angle). + - `text`: [string=''] The text to be displayed. Note that you can escape ; via \. + Available variables in text: *date* formats current date as --, + *time24* formats current time in 24-hour format, + *boardTitle* the title from the source board, + *boardDate* the date from the source board, + *boardRevision* the revision from the source board, + *boardCompany* the company from the source board, + *boardComment1*-*boardComment9* comments from the source board. + - `thickness`: [number|string] Stroke thickness. + - `type`: [string='simple'] [simple] Currently fixed. + - `vjustify`: [string='center'] [left,right,center] Vertical justification of the text. + - `voffset`: [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system. + - `width`: [number|string] Width of the characters (the same parameters as KiCAD uses). + - `text4`: [dict] Used to add text to the panel. + * Valid keys: + - `anchor`: [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + (middle of sides), c (center). The anchors refer to the panel outline. + - `height`: [number|string] Height of the characters (the same parameters as KiCAD uses). + - `hjustify`: [string='center'] [left,right,center] Horizontal justification of the text. + - `hoffset`: [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system. + - `layer`: [string='F.SilkS'] Specify text layer. + - `orientation`: [number|string] Specify the orientation (angle). + - `text`: [string=''] The text to be displayed. Note that you can escape ; via \. + Available variables in text: *date* formats current date as --, + *time24* formats current time in 24-hour format, + *boardTitle* the title from the source board, + *boardDate* the date from the source board, + *boardRevision* the revision from the source board, + *boardCompany* the company from the source board, + *boardComment1*-*boardComment9* comments from the source board. + - `thickness`: [number|string] Stroke thickness. + - `type`: [string='simple'] [simple] Currently fixed. + - `vjustify`: [string='center'] [left,right,center] Vertical justification of the text. + - `voffset`: [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system. + - `width`: [number|string] Width of the characters (the same parameters as KiCAD uses). + - **`output`**: [string='%f-%i%I%v.%x'] Filename for the output (%i=panel, %x=kicad_pcb). Affected by global options. + - `default_angles`: [string='deg'] [deg,°,rad] Angles used when omitted. + - `default_units`: [string='mm'] [mm,cm,dm,m,mil,inch,in] Units used when omitted. + - `dnf_filter`: [string|list(string)='_none'] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill. + - `pre_transform`: [string|list(string)='_none'] Name of the filter to transform fields before applying other filters. + A short-cut to use for simple cases where a variant is an overkill. + - `title`: [string=''] Text used to replace the sheet title. %VALUE expansions are allowed. + If it starts with `+` the text is concatenated. + - `variant`: [string=''] Board variant to apply. + - `category`: [string|list(string)=''] The category for this output. If not specified an internally defined category is used. + Categories looks like file system paths, i.e. PCB/fabrication/gerber. + - `disable_run_by_default`: [string|boolean] Use it to disable the `run_by_default` status of other output. + Useful when this output extends another and you don't want to generate the original. + Use the boolean true value to disable the output you are extending. + - `extends`: [string=''] Copy the `options` section from the indicated output. + - `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output. + - `priority`: [number=50] [0,100] Priority for this output. High priority outputs are created first. + Internally we use 10 for low priority, 90 for high priority and 50 for most outputs. + - `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested. + * PCB Print * Type: `pcb_print` * Description: Prints the PCB using a mechanism that is more flexible than `pdf_pcb_print` and `svg_pcb_print`. @@ -4533,7 +4828,7 @@ This case is [discussed here](docs/1_SCH_2_part_PCBs) - **Original KiCad Automation Scripts**: Scott Bezek, Productize SPRL - **KiBoM**: Oliver Henry Walters (@SchrodingersGat) - **Interactive HTML BoM**: @qu1ck -- **PcbDraw/Populate**: Jan Mrázek (@yaqwsx) +- **PcbDraw/Populate/KiKit**: Jan Mrázek (@yaqwsx) - **KiCost**: Dave Vandenbout (@devbisme) and Hildo Guillardi Júnior (@hildogjr) - **KiCAD to Boardview exporter**: @whitequark - **S-expression parser**: Takafumi Arakaki diff --git a/docs/README.in b/docs/README.in index ede17651..16623c57 100644 --- a/docs/README.in +++ b/docs/README.in @@ -1907,7 +1907,7 @@ This case is [discussed here](docs/1_SCH_2_part_PCBs) - **Original KiCad Automation Scripts**: Scott Bezek, Productize SPRL - **KiBoM**: Oliver Henry Walters (@SchrodingersGat) - **Interactive HTML BoM**: @qu1ck -- **PcbDraw/Populate**: Jan Mrázek (@yaqwsx) +- **PcbDraw/Populate/KiKit**: Jan Mrázek (@yaqwsx) - **KiCost**: Dave Vandenbout (@devbisme) and Hildo Guillardi Júnior (@hildogjr) - **KiCAD to Boardview exporter**: @whitequark - **S-expression parser**: Takafumi Arakaki diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index e82cb959..b2753047 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -1206,6 +1206,420 @@ outputs: format: 'classic' # [string='%f-%i%I%v.%x'] Filename for the output (%i=netlist/IPC-D-356, %x=net/d356). Affected by global options output: '%f-%i%I%v.%x' + # Panelize: + # It currently uses the KiKit tool, which must be available. + # Consult KiKit docs for detailed information. + # Note that you don't need to specify the units for all distances. + # If they are omitted they are assumed to be `default_units`. + # The same is valid for angles, using `default_angles` + - name: 'panelize_example' + comment: 'Creates a panel to fabricate various copies of the PCB at once.' + type: 'panelize' + dir: 'Example/panelize_dir' + options: + # [list(dict)|list(string)|string] One or more configurations used to create the panel. + # Use a string to include an external configuration, i.e. `myDefault.json`. + # You can also include a preset using `:name`, i.e. `:vcuts`. + # Use a dict to specify the options using the KiBot YAML file + configs: + # [dict] Fill non-board areas of the panel with copper + - copperfill: + # [number|string] Extra clearance from the board perimeters. Suitable for, e.g., not filling the tabs with + # copper + clearance: 0.5 + # [string|list(string)] List of layers to fill. Can be a comma-separated string. + # Using *all* means all external copper layers + layers: 'F.Cu,B.Cu' + # [number|string] The orientation of the hatched strokes + orientation: 45 + # [number|string] The space between the hatched strokes + spacing: 1 + # [string='none'] [none,solid,hatched] How to fill non-board areas of the panel with copper + type: 'none' + # [number|string] The width of the hatched strokes + width: 1 + # [dict] Specify how to perform the cuts on the tabs separating the board + cuts: + # [number|string] Specify clearance for copper around V-cuts + clearance: 0 + # `cut_curves` is an alias for `cutcurves` + # [boolean=false] Specify if curves should be approximated by straight cuts (e.g., for cutting tabs on circular boards). + # Used for *vcuts* + cutcurves: false + # [number|string] Drill size used for the *mousebites* + drill: 0.5 + # [string='Cmts.User'] Specify the layer to render V-cuts on. Also used for the *layer* type + layer: 'Cmts.User' + # [number|string] Specify the *mousebites* and *vcuts* offset, positive offset puts the cuts into the board, + # negative puts the cuts into the tabs + offset: 0 + # [number|string] Distance for tangential prolongation of the cuts (to cut through the internal corner fillets + # caused by milling). Used for *mousebites* and *layer* + prolong: 0 + # [number|string] The spacing of the holes used for the *mousebites* + spacing: 0.8 + # [string='none'] [none,mousebites,vcuts,layer] Layer: When KiKit reports it cannot perform cuts, you can render the cuts + # into a layer with this option to understand what's going on. Shouldn't be used for the final design + type: 'none' + # [dict] Debug options + debug: + # [boolean=false] Deterministic + deterministic: false + # [boolean=false] Draw backbone lines + drawBackboneLines: false + # [boolean=false] Draw partition lines + drawPartitionLines: false + # [boolean=false] Draw boxes + drawboxes: false + # [boolean=false] Draw tab fail + drawtabfail: false + # [boolean=false] Trace + trace: false + # [dict] Used to add fiducial marks to the (rail/frame of) the panel + fiducials: + # `copper_size` is an alias for `coppersize` + # [number|string] Diameter of the copper spot + coppersize: 1 + # [number|string] Horizontal offset from panel edges + hoffset: 0 + # [number|string] Diameter of the solder mask opening + opening: 1 + # [string='none'] [none,3fid,4fid] Add none, 3 or 4 fiducials to the (rail/frame of) the panel + type: 'none' + # [number|string] Vertical offset from panel edges + voffset: 0 + # [dict] Specify the frame around the boards + framing: + # [number|string] Specify the size of chamfer frame corners + chamfer: 0 + # [string='both'] [none,both,v,h] Specify whether to add cuts to the corners of the frame for easy removal. + # Used for *frame* + cuts: 'both' + # [number|string] Specify radius of fillet frame corners + fillet: 0 + # [number|string] Specify the horizontal space between PCB and the frame/rail + hspace: 2 + # `min_total_height` is an alias for `mintotalheight` + # `min_total_width` is an alias for `mintotalwidth` + # [number|string] If needed, add extra material to the rail or frame to meet the minimal requested size. + # Useful for services that require minimal panel size + mintotalheight: 0 + # [number|string] If needed, add extra material to the rail or frame to meet the minimal requested size. + # Useful for services that require minimal panel size + mintotalwidth: 0 + # `slot_width` is an alias for `slotwidth` + # [number|string] Width of the milled slot for *tightframe* + slotwidth: 2 + # [number|string] Specify the space between PCB and the frame/rail. Overrides `hspace` and `vspace` + space: null + # [string='none'] [none,railstb,railslr,frame,tightframe] Railstb: Add rails on top and bottom. + # Railslr: Add rails on left and right. + # Frame: Add a frame around the board. + # Tighframe: Add a frame around the board which fills the whole area of the panel - + # the boards have just a milled slot around their perimeter + type: 'none' + # [number|string] Specify the vertical space between PCB and the frame/rail + vspace: 2 + # [number|string] Specify with of the rails or frame + width: 5 + # [dict] Layout used for the panel + layout: + # [string='none'] [none,rows,cols,rowsCols] Specify alternations of board rotation. + # none: Do not alternate. + # rows: Rotate boards by 180° on every next row. + # cols: Rotate boards by 180° on every next column. + # rowsCols: Rotate boards by 180° based on a chessboard pattern + alternation: 'none' + # `bake_text` is an alias for `baketext` + # [boolean=true] A flag that indicates if text variables should be substituted or not + baketext: true + # [number=1] Specify the number of columns of boards in the grid pattern + cols: 1 + # [number|string] The width of horizontal backbone (0 means no backbone). The backbone does not increase the + # spacing of the boards + hbackbone: 0 + # [boolean=true] If there are both backbones specified, specifies if there should be a horizontal cut where the backbones + # cross + hbonecut: true + # [number=0] Skip every n horizontal backbones. I.e., 1 means place only every other backbone + hboneskip: 0 + # [number|string] Specify the horizontal gap between the boards + hspace: 0 + # `rename_net` is an alias for `renamenet` + # `rename_ref` is an alias for `renameref` + # [string='Board_{n}-{orig}'] A pattern by which to rename the nets. You can use {n} and {orig} to get the board number and original name + renamenet: 'Board_{n}-{orig}' + # [string='{orig}'] A pattern by which to rename the references. You can use {n} and {orig} to get the board number and original + # name + renameref: '{orig}' + # [number|string] Rotate the boards before placing them in the panel + rotation: 0 + # [number=1] Specify the number of rows of boards in the grid pattern + rows: 1 + # [number|string] Specify the gap between the boards, overwrites `hspace` and `vspace` + space: null + # [string='grid'] [grid] Currently fixed + type: 'grid' + # [number|string] The width of vertical backbone (0 means no backbone). The backbone does not increase the + # spacing of the boards + vbackbone: 0 + # [boolean=true] If there are both backbones specified, specifies if there should be a vertical cut where the backbones + # cross + vbonecut: true + # [number=0] Skip every n vertical backbones. I.e., 1 means place only every other backbone + vboneskip: 0 + # [number|string] Specify the vertical gap between the boards + vspace: 0 + # [dict] Sets page size on the resulting panel and position the panel in the page + page: + # [string='tl'] [tl,tr,bl,br,mt,mb,ml,mr,c] Point of the panel to be placed at given position. Can be one of tl, tr, bl, br + # (corners), mt, mb, ml, mr (middle of sides), c (center). The anchors refer to the panel outline + anchor: 'tl' + # [number|string] Height for the `custom` paper size + height: 210 + # `page_size` is an alias for `type` + # `pos_x` is an alias for `posx` + # `pos_y` is an alias for `posy` + # [number|string] The X position of the panel on the page + posx: 15 + # [number|string] The Y position of the panel on the page + posy: 15 + # `size` is an alias for `type` + # [string='inherit'] [inherit,custom,A0,A1,A2,A3,A4,A5,A,B,C,D,E,USLetter,USLegal,USLedger,A0-portrait,A1-portrait,A2-portrait, + # A3-portrait,A4-portrait,A5-portrait,A-portrait,B-portrait,C-portrait,D-portrait,E-portrait, + # USLetter-portrait,USLegal-portrait,USLedger-portrait] Paper size. The default `inherit` option inherits + # paper size from the source board. This feature is not supported on KiCAD 5 + type: 'inherit' + # [number|string] Width for the `custom` paper size + width: 297 + # [dict] Finishing touches to the panel + post: + # [boolean=false] Fill tabs and frame with copper (e.g., to save etchant or to increase rigidity of flex-PCB panels) + copperfill: false + # `mill_radius` is an alias for `millradius` + # [number|string] Simulate the milling operation (add fillets to the internal corners). + # Specify mill radius (usually 1 mm). 0 radius disables the functionality + millradius: 0 + # [string='tl'] [tl,tr,bl,br,mt,mb,ml,mr,c] Specify if the auxiliary origin an grid origin should be placed. + # Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr (middle of sides), c (center). + # Empty string does not changes the origin + origin: 'tl' + # `reconstruct_arcs` is an alias for `reconstructarcs` + # [boolean=false] The panelization process works on top of a polygonal representation of the board. + # This options allows to reconstruct the arcs in the design before saving the panel + reconstructarcs: false + # `refill_zones` is an alias for `refillzones` + # [boolean=false] Refill the user zones after the panel is build. + # This is only necessary when you want your zones to avoid cuts in panel + refillzones: false + # [string=''] A path to custom Python file. The file should contain a function kikitPostprocess(panel, args) that + # receives the prepared panel as the kikit.panelize.Panel object and the user-supplied arguments as a + # string - see `scriptarg`. The function can make arbitrary changes to the panel - you can append text, + # footprints, alter labels, etc. The function is invoked after the whole panel is constructed + # (including all other postprocessing). If you try to add a functionality for a common fabrication + # houses via scripting, consider submitting PR for KiKit + script: '' + # `script_arg` is an alias for `scriptarg` + # [string=''] An arbitrary string passed to the user post-processing script specified in script + scriptarg: '' + # [string='auto'] [auto] Currently fixed + type: 'auto' + # [dict] Style of the tabs used to join the PCB copies + tabs: + # [number|string] When your design features open pockets on the side, this parameter specifies extra cutout depth in order to + # ensure that a sharp corner of the pocket can be milled. Used for *full* + cutout: 1 + # [number=1] Number of tabs in the horizontal direction. Used for *fixed* + hcount: 1 + # [number|string] The width of tabs in the horizontal direction. Used for *fixed* and *spacing* + hwidth: 3 + # `min_distance` is an alias for `mindistance` + # [number|string] Minimal spacing between the tabs. If there are too many tabs, their count is reduced. + # Used for *fixed* + mindistance: 0 + # [number|string] The maximum spacing of the tabs. Used for *spacing* + spacing: 10 + # `tab_footprints` is an alias for `tabfootprints` + # [string='kikit:Tab'] The footprint/s used for the *annotation* type. You can specify a list of footprints separated by comma + tabfootprints: 'kikit:Tab' + # [string='spacing'] [fixed,spacing,full,annotation] Fixed: Place given number of tabs on the PCB edge. + # Spacing: Place tabs on the PCB edges based on spacing. + # Full: Create tabs that are full width of the PCB. + # Corner: Create tabs in the corners of the PCB. + # Annotation: Add tabs based on PCB annotations + type: 'spacing' + # [number=1] Number of tabs in the vertical direction. Used for *fixed* + vcount: 1 + # [number|string] The width of tabs in the vertical direction. Used for *fixed* and *spacing* + vwidth: 3 + # [number|string] The width of tabs in both directions. Overrides both `vwidth` and `hwidth`. + # Used for *fixed*, *spacing*, *corner* and *annotation* + width: null + # [dict] Used to add text to the panel + text: + # [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + # (middle of sides), c (center). The anchors refer to the panel outline + anchor: 'mt' + # [number|string] Height of the characters (the same parameters as KiCAD uses) + height: 1.5 + # [string='center'] [left,right,center] Horizontal justification of the text + hjustify: 'center' + # [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system + hoffset: 0 + # [string='F.SilkS'] Specify text layer + layer: 'F.SilkS' + # [number|string] Specify the orientation (angle) + orientation: 0 + # [string=''] The text to be displayed. Note that you can escape ; via \. + # Available variables in text: *date* formats current date as --, + # *time24* formats current time in 24-hour format, + # *boardTitle* the title from the source board, + # *boardDate* the date from the source board, + # *boardRevision* the revision from the source board, + # *boardCompany* the company from the source board, + # *boardComment1*-*boardComment9* comments from the source board + text: '' + # [number|string] Stroke thickness + thickness: 0.3 + # [string='simple'] [simple] Currently fixed + type: 'simple' + # [string='center'] [left,right,center] Vertical justification of the text + vjustify: 'center' + # [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system + voffset: 0 + # [number|string] Width of the characters (the same parameters as KiCAD uses) + width: 1.5 + # [dict] Used to add text to the panel + text2: + # [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + # (middle of sides), c (center). The anchors refer to the panel outline + anchor: 'mt' + # [number|string] Height of the characters (the same parameters as KiCAD uses) + height: 1.5 + # [string='center'] [left,right,center] Horizontal justification of the text + hjustify: 'center' + # [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system + hoffset: 0 + # [string='F.SilkS'] Specify text layer + layer: 'F.SilkS' + # [number|string] Specify the orientation (angle) + orientation: 0 + # [string=''] The text to be displayed. Note that you can escape ; via \. + # Available variables in text: *date* formats current date as --, + # *time24* formats current time in 24-hour format, + # *boardTitle* the title from the source board, + # *boardDate* the date from the source board, + # *boardRevision* the revision from the source board, + # *boardCompany* the company from the source board, + # *boardComment1*-*boardComment9* comments from the source board + text: '' + # [number|string] Stroke thickness + thickness: 0.3 + # [string='simple'] [simple] Currently fixed + type: 'simple' + # [string='center'] [left,right,center] Vertical justification of the text + vjustify: 'center' + # [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system + voffset: 0 + # [number|string] Width of the characters (the same parameters as KiCAD uses) + width: 1.5 + # [dict] Used to add text to the panel + text3: + # [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + # (middle of sides), c (center). The anchors refer to the panel outline + anchor: 'mt' + # [number|string] Height of the characters (the same parameters as KiCAD uses) + height: 1.5 + # [string='center'] [left,right,center] Horizontal justification of the text + hjustify: 'center' + # [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system + hoffset: 0 + # [string='F.SilkS'] Specify text layer + layer: 'F.SilkS' + # [number|string] Specify the orientation (angle) + orientation: 0 + # [string=''] The text to be displayed. Note that you can escape ; via \. + # Available variables in text: *date* formats current date as --, + # *time24* formats current time in 24-hour format, + # *boardTitle* the title from the source board, + # *boardDate* the date from the source board, + # *boardRevision* the revision from the source board, + # *boardCompany* the company from the source board, + # *boardComment1*-*boardComment9* comments from the source board + text: '' + # [number|string] Stroke thickness + thickness: 0.3 + # [string='simple'] [simple] Currently fixed + type: 'simple' + # [string='center'] [left,right,center] Vertical justification of the text + vjustify: 'center' + # [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system + voffset: 0 + # [number|string] Width of the characters (the same parameters as KiCAD uses) + width: 1.5 + # [dict] Used to add text to the panel + text4: + # [string='mt'] [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + # (middle of sides), c (center). The anchors refer to the panel outline + anchor: 'mt' + # [number|string] Height of the characters (the same parameters as KiCAD uses) + height: 1.5 + # [string='center'] [left,right,center] Horizontal justification of the text + hjustify: 'center' + # [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system + hoffset: 0 + # [string='F.SilkS'] Specify text layer + layer: 'F.SilkS' + # [number|string] Specify the orientation (angle) + orientation: 0 + # [string=''] The text to be displayed. Note that you can escape ; via \. + # Available variables in text: *date* formats current date as --, + # *time24* formats current time in 24-hour format, + # *boardTitle* the title from the source board, + # *boardDate* the date from the source board, + # *boardRevision* the revision from the source board, + # *boardCompany* the company from the source board, + # *boardComment1*-*boardComment9* comments from the source board + text: '' + # [number|string] Stroke thickness + thickness: 0.3 + # [string='simple'] [simple] Currently fixed + type: 'simple' + # [string='center'] [left,right,center] Vertical justification of the text + vjustify: 'center' + # [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system + voffset: 0 + # [number|string] Width of the characters (the same parameters as KiCAD uses) + width: 1.5 + # [dict] Used to add tooling holes to the (rail/frame of) the panel + tooling: + # [number|string] Horizontal offset from panel edges + hoffset: 0 + # [boolean=false] If True, the holes are included in the paste layer (therefore they appear on the stencil) + paste: false + # [number|string] Diameter of the holes + size: 1.152 + # [string='none'] [none,3hole,4hole] Add none, 3 or 4 holes to the (rail/frame of) the panel + type: 'none' + # [number|string] Vertical offset from panel edges + voffset: 0 + # [string='deg'] [deg,°,rad] Angles used when omitted + default_angles: 'deg' + # [string='mm'] [mm,cm,dm,m,mil,inch,in] Units used when omitted + default_units: 'mm' + # [string|list(string)='_none'] Name of the filter to mark components as not fitted. + # A short-cut to use for simple cases where a variant is an overkill + dnf_filter: '_none' + # [string='%f-%i%I%v.%x'] Filename for the output (%i=panel, %x=kicad_pcb). Affected by global options + output: '%f-%i%I%v.%x' + # [string|list(string)='_none'] Name of the filter to transform fields before applying other filters. + # A short-cut to use for simple cases where a variant is an overkill + pre_transform: '_none' + # [string=''] Text used to replace the sheet title. %VALUE expansions are allowed. + # If it starts with `+` the text is concatenated + title: '' + # [string=''] Board variant to apply + variant: '' # PCB Print: # Supports PDF, SVG, PNG, EPS and PS formats. # KiCad 5: including the frame is slow. diff --git a/kibot/misc.py b/kibot/misc.py index 38c97bfc..8be17002 100644 --- a/kibot/misc.py +++ b/kibot/misc.py @@ -240,6 +240,7 @@ W_PARITY = '(W101) ' W_MISSFPINFO = '(W102) ' W_PCBDRAW = '(W103) ' W_NOCRTYD = '(W104) ' +W_PANELEMPTY = '(W105) ' # Somehow arbitrary, the colors are real, but can be different PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"} PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e", diff --git a/kibot/out_panelize.py b/kibot/out_panelize.py new file mode 100644 index 00000000..be5f5106 --- /dev/null +++ b/kibot/out_panelize.py @@ -0,0 +1,627 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Salvador E. Tropea +# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial +# License: GPL-3.0 +# Project: KiBot (formerly KiPlot) +""" +Dependencies: + - name: KiKit + github: yaqwsx/KiKit + pypi: KiKit + downloader: pytool + role: mandatory +""" +import os +import re +import json +from tempfile import NamedTemporaryFile +from .error import KiPlotConfigurationError +from .gs import GS +from .kiplot import run_command +from .layer import Layer +from .misc import W_PANELEMPTY +from .optionable import BaseOptions +from .out_base import VariantOptions +from .macros import macros, document, output_class # noqa: F401 +from . import log + +logger = log.get_logger() + + +class PanelOptions(BaseOptions): + _num_regex = re.compile(r'([\d\.]+)(mm|cm|dm|m|mil|inch|in)') + _ang_regex = re.compile(r'([\d\.]+)(deg|°|rad)') + + def add_units(self, ops): + for op in ops: + val = getattr(self, op) + if val is None: + continue + if isinstance(val, (int, float)): + setattr(self, op, str(val)+self._parent._parent.default_units) + else: + m = PanelOptions._num_regex.match(val) + if m is None: + raise KiPlotConfigurationError('Malformed value `{}: {}` must be a number and units'.format(op, val)) + num = m.group(1) + try: + num_d = float(num) + except ValueError: + num_d = None + if num_d is None: + raise KiPlotConfigurationError('Malformed number in `{}` ({})'.format(op, num)) + + def add_angle(self, ops): + for op in ops: + val = getattr(self, op) + if isinstance(val, (int, float)): + setattr(self, op, str(val)+self._parent._parent.default_angles) + else: + m = PanelOptions._ang_regex.match(val) + if m is None: + raise KiPlotConfigurationError('Malformed angle `{}: {}` must be a number and its type'.format(op, val)) + num = m.group(1) + try: + num_d = float(num) + except ValueError: + num_d = None + if num_d is None: + raise KiPlotConfigurationError('Malformed number in `{}` ({})'.format(op, num)) + + +class PanelizePage(PanelOptions): + def __init__(self): + with document: + self.type = 'inherit' + """ *[inherit,custom,A0,A1,A2,A3,A4,A5,A,B,C,D,E,USLetter,USLegal,USLedger,A0-portrait,A1-portrait,A2-portrait, + A3-portrait,A4-portrait,A5-portrait,A-portrait,B-portrait,C-portrait,D-portrait,E-portrait, + USLetter-portrait,USLegal-portrait,USLedger-portrait] Paper size. The default `inherit` option inherits + paper size from the source board. This feature is not supported on KiCAD 5 """ + self.page_size = None + """ {type} """ + self.size = None + """ {type} """ + self.anchor = 'tl' + """ [tl,tr,bl,br,mt,mb,ml,mr,c] Point of the panel to be placed at given position. Can be one of tl, tr, bl, br + (corners), mt, mb, ml, mr (middle of sides), c (center). The anchors refer to the panel outline """ + self.posx = 15 + """ [number|string] The X position of the panel on the page """ + self.pos_x = None + """ {posx} """ + self.posy = 15 + """ [number|string] The Y position of the panel on the page """ + self.pos_y = None + """ {posy} """ + self.width = 297 + """ [number|string] Width for the `custom` paper size """ + self.height = 210 + """ [number|string] Height for the `custom` paper size """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('posx', 'posy', 'width', 'height')) + + +class PanelizeLayout(PanelOptions): + def __init__(self): + with document: + self.type = 'grid' + """ [grid] Currently fixed """ + self.hspace = 0 + """ [number|string] Specify the horizontal gap between the boards """ + self.vspace = 0 + """ [number|string] Specify the vertical gap between the boards """ + self.space = None + """ [number|string] Specify the gap between the boards, overwrites `hspace` and `vspace` """ + self.rotation = 0 + """ [number|string] Rotate the boards before placing them in the panel """ + self.renamenet = 'Board_{n}-{orig}' + """ A pattern by which to rename the nets. You can use {n} and {orig} to get the board number and original name """ + self.rename_net = None + """ {renamenet} """ + self.renameref = '{orig}' + """ A pattern by which to rename the references. You can use {n} and {orig} to get the board number and original + name """ + self.rename_ref = None + """ {renameref} """ + self.baketext = True + """ A flag that indicates if text variables should be substituted or not """ + self.bake_text = None + """ {baketext} """ + self.rows = 1 + """ *Specify the number of rows of boards in the grid pattern """ + self.cols = 1 + """ *Specify the number of columns of boards in the grid pattern """ + self.alternation = 'none' + """ [none,rows,cols,rowsCols] Specify alternations of board rotation. + none: Do not alternate. + rows: Rotate boards by 180° on every next row. + cols: Rotate boards by 180° on every next column. + rowsCols: Rotate boards by 180° based on a chessboard pattern """ + self.vbackbone = 0 + """ [number|string] The width of vertical backbone (0 means no backbone). The backbone does not increase the + spacing of the boards """ + self.hbackbone = 0 + """ [number|string] The width of horizontal backbone (0 means no backbone). The backbone does not increase the + spacing of the boards """ + self.vboneskip = 0 + """ Skip every n vertical backbones. I.e., 1 means place only every other backbone """ + self.hboneskip = 0 + """ Skip every n horizontal backbones. I.e., 1 means place only every other backbone """ + self.vbonecut = True + """ If there are both backbones specified, specifies if there should be a vertical cut where the backbones + cross """ + self.hbonecut = True + """ If there are both backbones specified, specifies if there should be a horizontal cut where the backbones + cross """ + super().__init__() + + def config(self, parent): + super().config(parent) + if self.space: + self.hspace = self.vspace = self.space + self.add_units(('vbackbone', 'hbackbone', 'hspace', 'vspace', 'space')) + self.add_angle(('rotation', )) + + +class PanelizeTabs(PanelOptions): + def __init__(self): + with document: + self.type = 'spacing' + """ [fixed,spacing,full,annotation] Fixed: Place given number of tabs on the PCB edge. + Spacing: Place tabs on the PCB edges based on spacing. + Full: Create tabs that are full width of the PCB. + Corner: Create tabs in the corners of the PCB. + Annotation: Add tabs based on PCB annotations """ + self.vwidth = 3 + """ [number|string] The width of tabs in the vertical direction. Used for *fixed* and *spacing* """ + self.hwidth = 3 + """ [number|string] The width of tabs in the horizontal direction. Used for *fixed* and *spacing* """ + self.width = None + """ [number|string] The width of tabs in both directions. Overrides both `vwidth` and `hwidth`. + Used for *fixed*, *spacing*, *corner* and *annotation* """ + self.vcount = 1 + """ Number of tabs in the vertical direction. Used for *fixed* """ + self.hcount = 1 + """ Number of tabs in the horizontal direction. Used for *fixed* """ + self.mindistance = 0 + """ [number|string] Minimal spacing between the tabs. If there are too many tabs, their count is reduced. + Used for *fixed* """ + self.min_distance = None + """ {mindistance} """ + self.spacing = 10 + """ [number|string] The maximum spacing of the tabs. Used for *spacing* """ + self.cutout = 1 + """ [number|string] When your design features open pockets on the side, this parameter specifies extra cutout depth in order to + ensure that a sharp corner of the pocket can be milled. Used for *full* """ + self.tabfootprints = 'kikit:Tab' + """ The footprint/s used for the *annotation* type. You can specify a list of footprints separated by comma """ + self.tab_footprints = None + """ {tabfootprints} """ + super().__init__() + + def config(self, parent): + super().config(parent) + if self.width: + self.vwidth = self.hwidth = self.width + self.add_units(('vwidth', 'hwidth', 'width', 'mindistance', 'spacing', 'cutout')) + + +class PanelizeCuts(PanelOptions): + def __init__(self): + with document: + self.type = 'none' + """ [none,mousebites,vcuts,layer] Layer: When KiKit reports it cannot perform cuts, you can render the cuts + into a layer with this option to understand what's going on. Shouldn't be used for the final design """ + self.drill = 0.5 + """ [number|string] Drill size used for the *mousebites* """ + self.spacing = 0.8 + """ [number|string] The spacing of the holes used for the *mousebites* """ + self.offset = 0 + """ [number|string] Specify the *mousebites* and *vcuts* offset, positive offset puts the cuts into the board, + negative puts the cuts into the tabs """ + self.prolong = 0 + """ [number|string] Distance for tangential prolongation of the cuts (to cut through the internal corner fillets + caused by milling). Used for *mousebites* and *layer* """ + self.clearance = 0 + """ [number|string] Specify clearance for copper around V-cuts """ + self.cutcurves = False + """ Specify if curves should be approximated by straight cuts (e.g., for cutting tabs on circular boards). + Used for *vcuts* """ + self.cut_curves = None + """ {cutcurves} """ + self.layer = 'Cmts.User' + """ Specify the layer to render V-cuts on. Also used for the *layer* type """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('drill', 'spacing', 'offset', 'prolong', 'clearance')) + res = Layer.solve(self.layer) + if len(res) > 1: + raise KiPlotConfigurationError('Must select only one layer for the V-cuts ({})'.format(self.layer)) + + +class PanelizeFraming(PanelOptions): + def __init__(self): + with document: + self.type = 'none' + """ [none,railstb,railslr,frame,tightframe] Railstb: Add rails on top and bottom. + Railslr: Add rails on left and right. + Frame: Add a frame around the board. + Tighframe: Add a frame around the board which fills the whole area of the panel - + the boards have just a milled slot around their perimeter """ + self.hspace = 2 + """ [number|string] Specify the horizontal space between PCB and the frame/rail """ + self.vspace = 2 + """ [number|string] Specify the vertical space between PCB and the frame/rail """ + self.space = None + """ [number|string] Specify the space between PCB and the frame/rail. Overrides `hspace` and `vspace` """ + self.width = 5 + """ [number|string] Specify with of the rails or frame """ + self.fillet = 0 + """ [number|string] Specify radius of fillet frame corners """ + self.chamfer = 0 + """ [number|string] Specify the size of chamfer frame corners """ + self.mintotalheight = 0 + """ [number|string] If needed, add extra material to the rail or frame to meet the minimal requested size. + Useful for services that require minimal panel size """ + self.min_total_height = None + """ {mintotalheight} """ + self.mintotalwidth = 0 + """ [number|string] If needed, add extra material to the rail or frame to meet the minimal requested size. + Useful for services that require minimal panel size """ + self.min_total_width = None + """ {mintotalwidth} """ + self.cuts = 'both' + """ [none,both,v,h] Specify whether to add cuts to the corners of the frame for easy removal. + Used for *frame* """ + self.slotwidth = 2 + """ [number|string] Width of the milled slot for *tightframe* """ + self.slot_width = None + """ {slotwidth} """ + super().__init__() + + def config(self, parent): + super().config(parent) + if self.space: + self.hspace = self.vspace = self.space + self.add_units(('hspace', 'vspace', 'space', 'width', 'fillet', 'chamfer', 'mintotalwidth', 'mintotalheight', + 'slotwidth')) + + +class PanelizeTooling(PanelOptions): + def __init__(self): + with document: + self.type = 'none' + """ [none,3hole,4hole] Add none, 3 or 4 holes to the (rail/frame of) the panel """ + self.hoffset = 0 + """ [number|string] Horizontal offset from panel edges """ + self.voffset = 0 + """ [number|string] Vertical offset from panel edges """ + self.size = 1.152 + """ [number|string] Diameter of the holes """ + self.paste = False + """ If True, the holes are included in the paste layer (therefore they appear on the stencil) """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('hoffset', 'voffset', 'size')) + + +class PanelizeFiducials(PanelOptions): + def __init__(self): + with document: + self.type = 'none' + """ [none,3fid,4fid] Add none, 3 or 4 fiducials to the (rail/frame of) the panel """ + self.hoffset = 0 + """ [number|string] Horizontal offset from panel edges """ + self.voffset = 0 + """ [number|string] Vertical offset from panel edges """ + self.coppersize = 1 + """ [number|string] Diameter of the copper spot """ + self.copper_size = None + """ {coppersize} """ + self.opening = 1 + """ [number|string] Diameter of the solder mask opening """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('hoffset', 'voffset', 'coppersize', 'opening')) + + +class PanelizeText(PanelOptions): + def __init__(self): + with document: + self.type = 'simple' + """ [simple] Currently fixed """ + self.text = '' + """ The text to be displayed. Note that you can escape ; via \\. + Available variables in text: *date* formats current date as --, + *time24* formats current time in 24-hour format, + *boardTitle* the title from the source board, + *boardDate* the date from the source board, + *boardRevision* the revision from the source board, + *boardCompany* the company from the source board, + *boardComment1*-*boardComment9* comments from the source board """ + self.anchor = 'mt' + """ [tl,tr,bl,br,mt,mb,ml,mr,c] Origin of the text. Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr + (middle of sides), c (center). The anchors refer to the panel outline """ + self.hoffset = 0 + """ [number|string] Specify the horizontal offset from anchor. Respects KiCAD coordinate system """ + self.voffset = 0 + """ [number|string] Specify the vertical offset from anchor. Respects KiCAD coordinate system """ + self.orientation = 0 + """ [number|string] Specify the orientation (angle) """ + self.width = 1.5 + """ [number|string] Width of the characters (the same parameters as KiCAD uses) """ + self.height = 1.5 + """ [number|string] Height of the characters (the same parameters as KiCAD uses) """ + self.hjustify = 'center' + """ [left,right,center] Horizontal justification of the text """ + self.vjustify = 'center' + """ [left,right,center] Vertical justification of the text """ + self.thickness = 0.3 + """ [number|string] Stroke thickness """ + self.layer = 'F.SilkS' + """ Specify text layer """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('hoffset', 'voffset', 'width', 'height', 'thickness')) + self.add_angle(('orientation', )) + res = Layer.solve(self.layer) + if len(res) > 1: + raise KiPlotConfigurationError('Must select only one layer for the text ({})'.format(self.layer)) + + +class PanelizeCopperfill(PanelOptions): + def __init__(self): + with document: + self.type = 'none' + """ [none,solid,hatched] How to fill non-board areas of the panel with copper """ + self.clearance = 0.5 + """ [number|string] Extra clearance from the board perimeters. Suitable for, e.g., not filling the tabs with + copper """ + self.layers = 'F.Cu,B.Cu' + """ [string|list(string)] List of layers to fill. Can be a comma-separated string. + Using *all* means all external copper layers """ + self.width = 1 + """ [number|string] The width of the hatched strokes """ + self.spacing = 1 + """ [number|string] The space between the hatched strokes """ + self.orientation = 45 + """ [number|string] The orientation of the hatched strokes """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('width', 'spacing', 'clearance')) + self.add_angle(('orientation', )) + if not isinstance(self.layers, str) or self.layers != 'all': + if isinstance(self.layers, str): + self.layers = self.layers.split(',') + res = Layer.solve(self.layers) + self.layers = ','.join([la.layer for la in res]) + + +class PanelizePost(PanelOptions): + def __init__(self): + with document: + self.type = 'auto' + """ [auto] Currently fixed """ + self.copperfill = False + """ Fill tabs and frame with copper (e.g., to save etchant or to increase rigidity of flex-PCB panels) """ + self.millradius = 0 + """ [number|string] Simulate the milling operation (add fillets to the internal corners). + Specify mill radius (usually 1 mm). 0 radius disables the functionality """ + self.mill_radius = None + """ {millradius} """ + self.reconstructarcs = False + """ The panelization process works on top of a polygonal representation of the board. + This options allows to reconstruct the arcs in the design before saving the panel """ + self.reconstruct_arcs = None + """ {reconstructarcs} """ + self.refillzones = False + """ Refill the user zones after the panel is build. + This is only necessary when you want your zones to avoid cuts in panel """ + self.refill_zones = None + """ {refillzones} """ + self.script = '' + """ A path to custom Python file. The file should contain a function kikitPostprocess(panel, args) that + receives the prepared panel as the kikit.panelize.Panel object and the user-supplied arguments as a + string - see `scriptarg`. The function can make arbitrary changes to the panel - you can append text, + footprints, alter labels, etc. The function is invoked after the whole panel is constructed + (including all other postprocessing). If you try to add a functionality for a common fabrication + houses via scripting, consider submitting PR for KiKit """ + self.scriptarg = '' + """ An arbitrary string passed to the user post-processing script specified in script """ + self.script_arg = None + """ {scriptarg} """ + self.origin = 'tl' + """ [tl,tr,bl,br,mt,mb,ml,mr,c] Specify if the auxiliary origin an grid origin should be placed. + Can be one of tl, tr, bl, br (corners), mt, mb, ml, mr (middle of sides), c (center). + Empty string does not changes the origin """ + super().__init__() + + def config(self, parent): + super().config(parent) + self.add_units(('millradius',)) + + +class PanelizeDebug(PanelOptions): + def __init__(self): + with document: + self.drawPartitionLines = False + """ Draw partition lines """ + self.drawBackboneLines = False + """ Draw backbone lines """ + self.drawboxes = False + """ Draw boxes """ + self.trace = False + """ Trace """ + self.deterministic = False + """ Deterministic """ + self.drawtabfail = False + """ Draw tab fail """ + super().__init__() + + +class PanelizeConfig(PanelOptions): + def __init__(self): + with document: + self.page = PanelizePage + """ *[dict] Sets page size on the resulting panel and position the panel in the page """ + self.layout = PanelizeLayout + """ *[dict] Layout used for the panel """ + self.tabs = PanelizeTabs + """ *[dict] Style of the tabs used to join the PCB copies """ + self.cuts = PanelizeCuts + """ *[dict] Specify how to perform the cuts on the tabs separating the board """ + self.framing = PanelizeFraming + """ *[dict] Specify the frame around the boards """ + self.tooling = PanelizeTooling + """ *[dict] Used to add tooling holes to the (rail/frame of) the panel """ + self.fiducials = PanelizeFiducials + """ *[dict] Used to add fiducial marks to the (rail/frame of) the panel """ + self.text = PanelizeText + """ [dict] Used to add text to the panel """ + self.text2 = PanelizeText + """ [dict] Used to add text to the panel """ + self.text3 = PanelizeText + """ [dict] Used to add text to the panel """ + self.text4 = PanelizeText + """ [dict] Used to add text to the panel """ + self.copperfill = PanelizeCopperfill + """ [dict] Fill non-board areas of the panel with copper """ + self.post = PanelizePost + """ [dict] Finishing touches to the panel """ + self.debug = PanelizeDebug + """ [dict] Debug options """ + super().__init__() + + def config(self, parent): + super().config(parent) + # Make None all things not specified + for k, v in self.get_attrs_gen(): + if isinstance(v, type): + setattr(self, k, None) + + +class PanelizeOptions(VariantOptions): + def __init__(self): + with document: + self.output = GS.def_global_output + """ *Filename for the output (%i=panel, %x=kicad_pcb) """ + self.configs = PanelizeConfig + """ *[list(dict)|list(string)|string] One or more configurations used to create the panel. + Use a string to include an external configuration, i.e. `myDefault.json`. + You can also include a preset using `:name`, i.e. `:vcuts`. + Use a dict to specify the options using the KiBot YAML file """ + self.title = '' + """ Text used to replace the sheet title. %VALUE expansions are allowed. + If it starts with `+` the text is concatenated """ + self.default_units = 'mm' + """ [mm,cm,dm,m,mil,inch,in] Units used when omitted """ + self.default_angles = 'deg' + """ [deg,°,rad] Angles used when omitted """ + super().__init__() + self._expand_id = 'panel' + self._expand_ext = 'kicad_pcb' + + def config(self, parent): + super().config(parent) + if isinstance(self.configs, type): + logger.warning(W_PANELEMPTY+'Generating a panel with default options, not very useful') + self.configs = [] + elif isinstance(self.configs, str): + self.configs = [self.configs] + + def create_config(self, cfg): + with NamedTemporaryFile(mode='w', delete=False, suffix='.json', prefix='kibot_panel_cfg') as f: + logger.debug('Writing panel config to '+f.name) + cfg_d = {} + for k, v in cfg.get_attrs_gen(): + if v: + cfg_d[k] = {k: v for k, v in v.get_attrs_gen() if v is not None} + js = json.dumps(cfg_d, indent=4) + logger.debugl(1, js) + f.write(js) + return f.name + + def run(self, output): + cmd_kikit = self.ensure_tool('KiKit') + super().run(output) + to_remove = [] + # Create the input PCB + if self._comps or self.title: + logger.debug('Creating modified PCB') + self.set_title(self.title) + self.filter_pcb_components(GS.board, do_3D=True) + fname = self.save_tmp_board() + self.unfilter_pcb_components(GS.board, do_3D=True) + self.restore_title() + to_remove.append(fname) + to_remove.append(fname.replace('kicad_pcb', 'kicad_pro')) + to_remove.append(fname.replace('kicad_pcb', 'kicad_prl')) + to_remove.append(fname.replace('kicad_pcb', 'pro')) + logger.debug('- Modified PCB: '+fname) + else: + fname = GS.pcb_file + + # Create the command + cmd = [cmd_kikit, 'panelize'] + # Add all the configurations + for cfg in self.configs: + cmd.append('--preset') + if isinstance(cfg, str): + if cfg[0] != ':' and not os.path.isfile(cfg): + raise KiPlotConfigurationError('Missing config file: '+cfg) + cmd.append(cfg) + else: + cfg_f = self.create_config(cfg) + to_remove.append(cfg_f) + cmd.append(cfg_f) + # Add the PCB and output + cmd.append(fname) + cmd.append(output) + try: + run_command(cmd) + finally: + # Remove temporals + for f in to_remove: + if os.path.isfile(f): + os.remove(f) + + def get_targets(self, out_dir): + return [self._parent.expand_filename(out_dir, self.output)] + + +@output_class +class Panelize(BaseOutput): # noqa: F821 + """ Panelize + Creates a panel to fabricate various copies of the PCB at once. + It currently uses the KiKit tool, which must be available. + Consult KiKit docs for detailed information. + Note that you don't need to specify the units for all distances. + If they are omitted they are assumed to be `default_units`. + The same is valid for angles, using `default_angles` """ + def __init__(self): + super().__init__() + with document: + self.options = PanelizeOptions + """ *[dict] Options for the `Panelize` output """ + self._category = 'PCB/fabrication' + + @staticmethod + def get_conf_examples(name, layers, templates): + outs = [] + for tpl in templates: + for out in tpl: + if out['type'] == 'panelize': + outs.append(out) + return outs diff --git a/kibot/resources/config_templates/panelize/panel_4x4.kibot.yaml b/kibot/resources/config_templates/panelize/panel_4x4.kibot.yaml new file mode 100644 index 00000000..275718e4 --- /dev/null +++ b/kibot/resources/config_templates/panelize/panel_4x4.kibot.yaml @@ -0,0 +1,62 @@ +# Example KiBot config file for a basic panel +kibot: + version: 1 + +outputs: + - name: 'panel' + comment: "Create a 4x4 complex panel" + type: panelize + dir: Panel + options: + title: '+ (Panel)' + default_units: mm + configs: + - layout: + rows: 4 + cols: 4 + space: 2 + hbackbone: 5 + vbackbone: 5 + hboneskip: 1 + vboneskip: 1 + page: + size: A0 + tabs: + type: fixed + width: 3 + vcount: 2 + hcount: 0 + cuts: + type: mousebites + drill: 0.5 + spacing: 1mm + offset: 0.2 + prolong: 0.5 + framing: + type: railstb + width: 5 + space: 3 + copperfill: + type: hatched + clearance: 2 + spacing: 0.5 + width: 0.5 + tooling: + type: 3hole + hoffset: 2.5 + voffset: 2.5 + size: 1.5 + fiducials: + type: 3fid + hoffset: 5 + voffset: 2.5 + coppersize: 2 + opening: 1 + text: + text: "Panel for {boardTitle}" + anchor: mt + voffset: 2.5 + hjustify: center + vjustify: center + post: + millradius: 1 diff --git a/src/kibot-check b/src/kibot-check index 168e1c0c..d2c28f05 100755 --- a/src/kibot-check +++ b/src/kibot-check @@ -548,6 +548,39 @@ deps = '{\ "url": "https://github.com/hildogjr/KiCost",\ "url_down": "https://github.com/hildogjr/KiCost/releases"\ },\ + "KiKit": {\ + "arch": null,\ + "command": "kikit",\ + "comments": [],\ + "deb_package": "kikit",\ + "downloader": {},\ + "downloader_str": "pytool",\ + "extra_arch": null,\ + "extra_deb": null,\ + "help_option": "--version",\ + "importance": 10000,\ + "in_debian": false,\ + "is_kicad_plugin": false,\ + "is_python": false,\ + "name": "KiKit",\ + "no_cmd_line_version": false,\ + "no_cmd_line_version_old": false,\ + "output": "panelize",\ + "plugin_dirs": null,\ + "pypi_name": "KiKit",\ + "roles": [\ + {\ + "desc": null,\ + "mandatory": true,\ + "max_version": null,\ + "output": "panelize",\ + "version": null\ + }\ + ],\ + "tests": [],\ + "url": "https://github.com/yaqwsx/KiKit",\ + "url_down": "https://github.com/yaqwsx/KiKit/releases"\ + },\ "LXML": {\ "arch": "python-lxml",\ "command": "lxml",\ diff --git a/tests/yaml_samples/panelize_1.kibot.yaml b/tests/yaml_samples/panelize_1.kibot.yaml new file mode 100644 index 00000000..67e510e0 --- /dev/null +++ b/tests/yaml_samples/panelize_1.kibot.yaml @@ -0,0 +1,61 @@ +# Example KiBot config file for a basic panel +kibot: + version: 1 + +outputs: + - name: 'panel' + comment: "Create a 4x4 complex panel" + type: panelize + options: + title: '+ (Panel)' + default_units: mm + configs: + - layout: + rows: 4 + cols: 4 + space: 2 + hbackbone: 5 + vbackbone: 5 + hboneskip: 1 + vboneskip: 1 + page: + size: A3 + tabs: + type: fixed + width: 3 + vcount: 2 + hcount: 0 + cuts: + type: mousebites + drill: 0.5 + spacing: 1mm + offset: 0.2 + prolong: 0.5 + framing: + type: railstb + width: 5 + space: 3 + copperfill: + type: hatched + clearance: 2 + spacing: 0.5 + width: 0.5 + tooling: + type: 3hole + hoffset: 2.5 + voffset: 2.5 + size: 1.5 + fiducials: + type: 3fid + hoffset: 5 + voffset: 2.5 + coppersize: 2 + opening: 1 + text: + text: My panel + anchor: mt + voffset: 2.5 + hjustify: center + vjustify: center + post: + millradius: 1