├── .gitignore ├── KiZip ├── __init__.py ├── core │ ├── KiZip.py │ ├── __init__.py │ ├── config.py │ ├── layers.py │ └── parser.py ├── dialog │ ├── __init__.py │ ├── dialog_base.py │ └── settings_dialog.py ├── dialog_test.py ├── errors.py ├── generate_gerber_package.py ├── icon.png └── version.py ├── LICENCE ├── README.md ├── __init__.py ├── icons └── zip.png └── settings_dialog.fbp /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | config.ini -------------------------------------------------------------------------------- /KiZip/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import threading 4 | import time 5 | 6 | import wx 7 | import wx.aui 8 | 9 | 10 | def check_for_bom_button(): 11 | # From Miles McCoo's blog 12 | # https://kicad.mmccoo.com/2017/03/05/adding-your-own-command-buttons-to-the-pcbnew-gui/ 13 | def find_pcbnew_window(): 14 | windows = wx.GetTopLevelWindows() 15 | pcbneww = [w for w in windows if "pcbnew" in w.GetTitle().lower()] 16 | if len(pcbneww) != 1: 17 | return None 18 | return pcbneww[0] 19 | 20 | def callback(_): 21 | plugin.Run() 22 | 23 | path = os.path.dirname(__file__) 24 | while not wx.GetApp(): 25 | time.sleep(1) 26 | bm = wx.Bitmap(path + '/icon.png', wx.BITMAP_TYPE_PNG) 27 | button_wx_item_id = 0 28 | 29 | from pcbnew import ID_H_TOOLBAR 30 | while True: 31 | time.sleep(1) 32 | pcbnew_window = find_pcbnew_window() 33 | if not pcbnew_window: 34 | continue 35 | 36 | top_tb = pcbnew_window.FindWindowById(ID_H_TOOLBAR) 37 | if button_wx_item_id == 0 or not top_tb.FindTool(button_wx_item_id): 38 | top_tb.AddSeparator() 39 | button_wx_item_id = wx.NewId() 40 | top_tb.AddTool(button_wx_item_id, "KiZip", bm, 41 | "Generate Gerber Package", wx.ITEM_NORMAL) 42 | top_tb.Bind(wx.EVT_TOOL, callback, id=button_wx_item_id) 43 | top_tb.Realize() 44 | 45 | if not os.environ.get('KIZIP_BOM_CLI_MODE', False): 46 | from .core.KiZip import KiZipPlugin 47 | 48 | plugin = KiZipPlugin() 49 | plugin.register() 50 | 51 | # Add a button the hacky way if plugin button is not supported 52 | # in pcbnew, unless this is linux. 53 | if not plugin.pcbnew_icon_support and not sys.platform.startswith('linux'): 54 | t = threading.Thread(target=check_for_bom_button) 55 | t.daemon = True 56 | t.start() 57 | -------------------------------------------------------------------------------- /KiZip/core/KiZip.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import re 4 | import sys 5 | from datetime import datetime 6 | import logging 7 | import wx 8 | 9 | import zipfile 10 | import shutil 11 | 12 | import pcbnew 13 | 14 | from .config import Config 15 | from ..dialog import SettingsDialog 16 | from ..errors import ParsingException 17 | from .parser import Parser 18 | 19 | 20 | 21 | 22 | class Logger(object): 23 | 24 | def __init__(self, cli=False): 25 | self.cli = cli 26 | self.logger = logging.getLogger('KiZip') 27 | self.logger.setLevel(logging.INFO) 28 | ch = logging.StreamHandler(sys.stdout) 29 | ch.setLevel(logging.INFO) 30 | formatter = logging.Formatter( 31 | "%(asctime)-15s %(levelname)s %(message)s") 32 | ch.setFormatter(formatter) 33 | self.logger.addHandler(ch) 34 | 35 | def info(self, *args): 36 | if self.cli: 37 | self.logger.info(*args) 38 | 39 | def error(self, msg): 40 | if self.cli: 41 | self.logger.error(msg) 42 | else: 43 | wx.MessageBox(msg) 44 | 45 | def warn(self, msg): 46 | if self.cli: 47 | self.logger.warn(msg) 48 | else: 49 | wx.LogWarning(msg) 50 | 51 | 52 | log = None # type: Logger or None 53 | 54 | 55 | def process_substitutions(output_name_format, pcb_file_name, metadata): 56 | # type: (str, str, dict)->str 57 | name = output_name_format.replace('%f', os.path.splitext(pcb_file_name)[0]) 58 | name = name.replace('%p', metadata['title']) 59 | name = name.replace('%c', metadata['company']) 60 | name = name.replace('%r', metadata['revision']) 61 | name = name.replace('%d', metadata['date'].replace(':', '-')) 62 | now = datetime.now() 63 | name = name.replace('%D', now.strftime('%Y-%m-%d')) 64 | name = name.replace('%T', now.strftime('%H-%M-%S')) 65 | # sanitize the name to avoid characters illegal in file systems 66 | name = name.replace('\\', '/') 67 | name = re.sub(r'[?%*:|"<>]', '_', name) 68 | return name + '.zip' 69 | 70 | class KiZipPlugin(pcbnew.ActionPlugin, object): 71 | 72 | def __init__(self): 73 | super(KiZipPlugin, self).__init__() 74 | self.name = "Generate Gerber Package" 75 | self.category = "Read PCB" 76 | self.pcbnew_icon_support = hasattr(self, "show_toolbar_button") 77 | self.show_toolbar_button = True 78 | icon_dir = os.path.dirname(os.path.dirname(__file__)) 79 | self.icon_file_name = os.path.join(icon_dir, 'icon.png') 80 | self.description = "Generate Gerber Package" 81 | 82 | def defaults(self): 83 | pass 84 | 85 | def Run(self): 86 | from ..version import version 87 | from ..errors import ParsingException 88 | self.version = version 89 | board = pcbnew.GetBoard() 90 | pcb_file_name = board.GetFileName() 91 | config = Config(self.version, os.path.dirname(pcb_file_name)) 92 | 93 | logger = Logger() 94 | if not pcb_file_name: 95 | logger.error('Please save the board file before generating gerbers') 96 | return 97 | 98 | parser = Parser(pcb_file_name, config, logger, board) 99 | try: 100 | run_with_dialog(parser, config, logger) 101 | except ParsingException as e: 102 | logger.error(str(e)) 103 | 104 | 105 | def main(parser, config, logger): 106 | # type: (Parser, Config, Logger) -> None 107 | global log 108 | log = logger 109 | pcb_file_name = os.path.basename(parser.file_name) 110 | pcb_file_dir = os.path.dirname(parser.file_name) 111 | 112 | 113 | pcbdata = parser.parse() 114 | 115 | # DRC Check! 116 | drc_errors = parser.drc_check() 117 | if len(drc_errors): 118 | errors = "" 119 | for e in drc_errors: 120 | errors += "😭️ {0} {1}\n".format(*e) 121 | wx.LogError("Unable to export! 😨️\n\n{0}".format(errors)) 122 | return 123 | 124 | file_list = parser.plot() 125 | logger.info(file_list) 126 | 127 | 128 | if os.path.isabs(config.output_dest_dir): 129 | output_file_dir = config.output_dest_dir 130 | else: 131 | output_file_dir = os.path.join(pcb_file_dir, config.output_dest_dir) 132 | 133 | output_file_name = process_substitutions( 134 | config.output_name_format, pcb_file_name, pcbdata['metadata']) 135 | output_file_name = os.path.join(output_file_dir, output_file_name) 136 | os.makedirs(output_file_dir, exist_ok=True) 137 | 138 | #zip up all files 139 | with zipfile.ZipFile(output_file_name, "w", zipfile.ZIP_DEFLATED) as zf: 140 | for filename in file_list: 141 | zf.write(filename=os.path.abspath(filename), arcname=os.path.basename(filename)) 142 | 143 | 144 | 145 | 146 | def run_with_dialog(parser, config, logger): 147 | # type: (Parser, Config, Logger) -> None 148 | def save_config(dialog_panel): 149 | config.set_from_dialog(dialog_panel) 150 | config.save() 151 | 152 | config.load_from_ini() 153 | dlg = SettingsDialog( 154 | config_save_func=save_config, 155 | file_name_format_hint=config.FILE_NAME_FORMAT_HINT, 156 | version=config.version 157 | ) 158 | try: 159 | config.transfer_to_dialog(dlg.panel) 160 | if dlg.ShowModal() == wx.ID_OK: 161 | config.set_from_dialog(dlg.panel) 162 | main(parser, config, logger) 163 | finally: 164 | dlg.Destroy() -------------------------------------------------------------------------------- /KiZip/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregdavill/KiZip/24c9eced7143faa103993d5534968c0185007b2a/KiZip/core/__init__.py -------------------------------------------------------------------------------- /KiZip/core/config.py: -------------------------------------------------------------------------------- 1 | """Config object""" 2 | 3 | import argparse 4 | import os 5 | import re 6 | 7 | from collections import namedtuple 8 | import pcbnew 9 | 10 | from wx import FileConfig 11 | import wx 12 | 13 | from .. import dialog 14 | from .layers import default_layers 15 | 16 | 17 | 18 | class Config: 19 | FILE_NAME_FORMAT_HINT = ( 20 | 'Output file name format supports substitutions:\n' 21 | '\n' 22 | ' %f : original pcb file name without extension.\n' 23 | ' %p : pcb/project title from pcb metadata.\n' 24 | ' %c : company from pcb metadata.\n' 25 | ' %r : revision from pcb metadata.\n' 26 | ' %d : pcb date from metadata if available, ' 27 | 'file modification date otherwise.\n' 28 | ' %D : gerber generation date.\n' 29 | ' %T : gerber generation time.\n' 30 | '\n' 31 | 'Extension will be added automatically.' 32 | ) # type: str 33 | 34 | # Helper constants 35 | config_file = os.path.join(os.path.dirname(__file__), '..', 'config.ini') 36 | 37 | # Defaults 38 | 39 | # General 40 | output_dest_dir = 'Production/' # This is relative to pcb file directory 41 | output_name_format = '%f_gerber_%D_%T' 42 | 43 | # Layers 44 | layers = default_layers 45 | 46 | @staticmethod 47 | def _split(s): 48 | """Splits string by ',' and drops empty strings from resulting array.""" 49 | return [a.replace('\\,', ',') for a in re.split(r'(? None 98 | 99 | # General 100 | self.output_dest_dir = dlg.general.outputDirPicker.Path 101 | self.output_name_format = dlg.general.fileNameFormatTextControl.Value 102 | 103 | # Layers 104 | for index in range(len(self.layers)): 105 | layer = self.layers[index] 106 | 107 | pnl = next(pnl for pnl,lyr in dlg.layers.layers if lyr.name is layer.name) 108 | if pnl is not None: 109 | layer.enabled = pnl.IsEnabled() 110 | layer.ext = pnl.GetExtension() 111 | 112 | 113 | def transfer_to_dialog(self, dlg): 114 | # type: (dialog.settings_dialog.SettingsDialogPanel) -> None 115 | 116 | # General 117 | import os.path 118 | if os.path.isabs(self.output_dest_dir): 119 | dlg.general.outputDirPicker.Path = self.output_dest_dir 120 | else: 121 | dlg.general.outputDirPicker.Path = os.path.join( 122 | self.rel_directory, self.output_dest_dir) 123 | dlg.general.fileNameFormatTextControl.Value = self.output_name_format 124 | 125 | # Layers 126 | for l in self.layers: 127 | dlg.layers.AddLayer(l) 128 | 129 | 130 | # noinspection PyTypeChecker 131 | def add_options(self, parser, file_name_format_hint): 132 | # type: (argparse.ArgumentParser, str) -> None 133 | parser.add_argument('--show-dialog', action='store_true', 134 | help='Shows config dialog. All other flags ' 135 | 'will be ignored.') 136 | 137 | # General 138 | parser.add_argument('--dest-dir', default=self.output_dest_dir, 139 | help='Destination directory for output file ' 140 | 'relative to pcb file directory.') 141 | parser.add_argument('--name-format', default=self.output_name_format, 142 | help=file_name_format_hint.replace('%', '%%')) 143 | 144 | def set_from_args(self, args): 145 | # type: (argparse.Namespace) -> None 146 | import math 147 | 148 | # General 149 | self.output_dest_dir = args.dest_dir 150 | self.output_name_format = args.name_format 151 | 152 | -------------------------------------------------------------------------------- /KiZip/core/layers.py: -------------------------------------------------------------------------------- 1 | 2 | import pcbnew 3 | 4 | class Layer: 5 | def __init__(self, id, name, ext, enabled=False): 6 | self.enabled = enabled 7 | self.name = name 8 | self.id = id 9 | self.ext = ext 10 | 11 | 12 | class GerberLayer(Layer): 13 | def __init__(self, id, name, ext, enabled=False): 14 | Layer.__init__(self, id, name, ext, enabled) 15 | 16 | class DrillLayer(Layer): 17 | def __init__(self, id, name, ext, enabled=False): 18 | Layer.__init__(self, id, name, ext, enabled) 19 | 20 | default_layers = [ 21 | GerberLayer(pcbnew.F_Cu ,'Top Copper', '.gtl', enabled=True), 22 | GerberLayer(pcbnew.In1_Cu ,'Inner1 Copper', '.g2', enabled=True), 23 | GerberLayer(pcbnew.In2_Cu ,'Inner2 Copper', '.g3', enabled=True), 24 | GerberLayer(pcbnew.In3_Cu ,'Inner3 Copper', '.g4'), 25 | GerberLayer(pcbnew.In4_Cu ,'Inner4 Copper', 'g5'), 26 | GerberLayer(pcbnew.In5_Cu ,'Inner5 Copper', '.g6'), 27 | GerberLayer(pcbnew.In6_Cu ,'Inner6 Copper', ''), 28 | GerberLayer(pcbnew.In7_Cu ,'Inner7 Copper', ''), 29 | GerberLayer(pcbnew.In8_Cu ,'Inner8 Copper', ''), 30 | GerberLayer(pcbnew.In9_Cu ,'Inner9 Copper', ''), 31 | GerberLayer(pcbnew.In10_Cu ,'Inner10 Copper', ''), 32 | GerberLayer(pcbnew.In11_Cu ,'Inner11 Copper', ''), 33 | GerberLayer(pcbnew.In12_Cu ,'Inner12 Copper', ''), 34 | GerberLayer(pcbnew.In13_Cu ,'Inner13 Copper', ''), 35 | GerberLayer(pcbnew.In14_Cu ,'Inner14 Copper', ''), 36 | GerberLayer(pcbnew.In15_Cu ,'Inner15 Copper', ''), 37 | GerberLayer(pcbnew.In16_Cu ,'Inner16 Copper', ''), 38 | GerberLayer(pcbnew.In17_Cu ,'Inner17 Copper', ''), 39 | GerberLayer(pcbnew.In18_Cu ,'Inner18 Copper', ''), 40 | GerberLayer(pcbnew.In19_Cu ,'Inner19 Copper', ''), 41 | GerberLayer(pcbnew.In20_Cu ,'Inner20 Copper', ''), 42 | GerberLayer(pcbnew.In21_Cu ,'Inner21 Copper', ''), 43 | GerberLayer(pcbnew.In22_Cu ,'Inner22 Copper', ''), 44 | GerberLayer(pcbnew.In23_Cu ,'Inner23 Copper', ''), 45 | GerberLayer(pcbnew.In24_Cu ,'Inner24 Copper', ''), 46 | GerberLayer(pcbnew.In25_Cu ,'Inner25 Copper', ''), 47 | GerberLayer(pcbnew.In26_Cu ,'Inner26 Copper', ''), 48 | GerberLayer(pcbnew.In27_Cu ,'Inner27 Copper', ''), 49 | GerberLayer(pcbnew.In28_Cu ,'Inner28 Copper', ''), 50 | GerberLayer(pcbnew.In29_Cu ,'Inner29 Copper', ''), 51 | GerberLayer(pcbnew.In30_Cu ,'Inner30 Copper', ''), 52 | GerberLayer(pcbnew.B_Cu ,'Back Copper', '.gbl', enabled=True), 53 | GerberLayer(pcbnew.F_Adhes ,'Top_Adhesive', ''), 54 | GerberLayer(pcbnew.B_Adhes ,'Back_Adhesive', ''), 55 | GerberLayer(pcbnew.F_Paste ,'Top Paste', '.gtp', enabled=True), 56 | GerberLayer(pcbnew.B_Paste ,'Back Paste', '.gbp', enabled=True), 57 | GerberLayer(pcbnew.F_SilkS ,'Top SilkScreen', '.gto', enabled=True), 58 | GerberLayer(pcbnew.B_SilkS ,'Back SilkScreen', '.gbo', enabled=True), 59 | GerberLayer(pcbnew.F_Mask ,'Top SolderMask', '.gts', enabled=True), 60 | GerberLayer(pcbnew.B_Mask ,'Back SolderMask', '.gbs', enabled=True), 61 | GerberLayer(pcbnew.Edge_Cuts,'Board Edges', '.gko', enabled=True), 62 | GerberLayer(pcbnew.Margin ,'Margin', ''), 63 | GerberLayer(pcbnew.F_CrtYd ,'Top CourtYard', ''), 64 | GerberLayer(pcbnew.B_CrtYd ,'Back CourtYard', ''), 65 | GerberLayer(pcbnew.F_Fab ,'Top Fab', ''), 66 | GerberLayer(pcbnew.B_Fab ,'Back Fab', ''), 67 | DrillLayer( 9000, 'Drills', '.drl', enabled=True) 68 | ] 69 | -------------------------------------------------------------------------------- /KiZip/core/parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import sys 4 | import re 5 | 6 | import pcbnew 7 | import time 8 | 9 | import logging 10 | 11 | 12 | from pcbnew import * 13 | from datetime import datetime 14 | from shutil import move 15 | 16 | from .layers import GerberLayer, DrillLayer 17 | 18 | class Parser(object): 19 | 20 | def __init__(self, file_name, config, logger, board=None): 21 | self.file_name = file_name 22 | self.config = config 23 | self.logger = logger 24 | self.output_folder = None 25 | self.temp_folder = None 26 | self.generated_files = [] 27 | 28 | self.board = board 29 | if self.board is None: 30 | self.board = pcbnew.LoadBoard(self.file_name) # type: pcbnew.BOARD 31 | 32 | def __del__(self): 33 | if self.temp_folder is not None: 34 | self.temp_folder.cleanup() 35 | 36 | def parse(self): 37 | title_block = self.board.GetTitleBlock() 38 | file_date = title_block.GetDate() 39 | if not file_date: 40 | file_mtime = os.path.getmtime(self.file_name) 41 | file_date = datetime.fromtimestamp(file_mtime).strftime( 42 | '%Y-%m-%d %H:%M:%S') 43 | title = title_block.GetTitle() 44 | pcb_file_name = os.path.basename(self.file_name) 45 | if not title: 46 | # remove .kicad_pcb extension 47 | title = os.path.splitext(pcb_file_name)[0] 48 | 49 | pcbdata = { 50 | "metadata": { 51 | "title": title, 52 | "revision": title_block.GetRevision(), 53 | "company": title_block.GetCompany(), 54 | "date": file_date, 55 | }, 56 | "layer_count": self.board.GetCopperLayerCount(), 57 | } 58 | 59 | return pcbdata 60 | 61 | def drc_check(self): 62 | drc_file = "drc.txt" 63 | pcbnew.WriteDRCReport(self.board, drc_file, pcbnew.GetUserUnits(), True) 64 | 65 | # Count DRC Errors 66 | matches = [] 67 | with open(drc_file, 'r') as f: 68 | for l in f: 69 | match = re.findall('^\*\* Found (\d+) (.+) \*\*', l) 70 | if len(match): 71 | if match[0][0] != '0': 72 | matches += match 73 | 74 | return matches 75 | 76 | def plot(self): 77 | 78 | self.temp_folder = tempfile.TemporaryDirectory(prefix="kizip") 79 | self.output_folder = self.temp_folder.name 80 | 81 | # Default 82 | pctl = PLOT_CONTROLLER(self.board) 83 | 84 | popt = pctl.GetPlotOptions() 85 | popt.SetOutputDirectory(self.output_folder) 86 | popt.SetPlotFrameRef(False) 87 | 88 | # Nightly doesn't like SetLineWidth 89 | try: 90 | popt.SetLineWidth(FromMM(0.35)) 91 | except AttributeError: 92 | pass 93 | 94 | # Set some important plot options: 95 | popt.SetPlotFrameRef(False) 96 | popt.SetAutoScale(False) 97 | popt.SetScale(1) 98 | popt.SetMirror(False) 99 | popt.SetUseGerberAttributes(False) 100 | popt.SetExcludeEdgeLayer(True) 101 | popt.SetScale(1) 102 | popt.SetUseAuxOrigin(False) 103 | popt.SetNegative(False) 104 | popt.SetPlotReference(True) 105 | popt.SetPlotValue(True) 106 | popt.SetPlotInvisibleText(False) 107 | popt.SetSubtractMaskFromSilk(True) #remove solder mask from silk to be sure there is no silk on pads 108 | popt.SetMirror(False) 109 | popt.SetDrillMarksType(PCB_PLOT_PARAMS.NO_DRILL_SHAPE) 110 | 111 | if hasattr(popt, 'SetDisableGerberMacros'): 112 | popt.SetDisableGerberMacros(True) 113 | if hasattr(popt, 'SetUseGerberX2format'): 114 | popt.SetUseGerberX2format(False) 115 | 116 | 117 | # Fabricators need drill files. 118 | # sometimes a drill map file is asked (for verification purpose) 119 | drlwriter = EXCELLON_WRITER( self.board ) 120 | drlwriter.SetMapFileFormat( PLOT_FORMAT_PDF ) 121 | 122 | mirror = False 123 | minimalHeader = False 124 | 125 | if popt.GetUseAuxOrigin(): 126 | def aux_origin_missing(): 127 | popt.SetUseAuxOrigin(False) 128 | return wxPoint(0, 0) 129 | offset = getattr(self.board, "GetAuxOrigin", aux_origin_missing)() 130 | else: 131 | offset = wxPoint(0,0) 132 | 133 | mergeNPTH = False 134 | drlwriter.SetOptions( mirror, minimalHeader, offset, mergeNPTH ) 135 | 136 | metricFmt = True 137 | drlwriter.SetFormat( metricFmt ) 138 | 139 | genDrl = True 140 | genMap = True 141 | drlwriter.CreateDrillandMapFilesSet( self.output_folder, genDrl, genMap ) 142 | 143 | 144 | 145 | 146 | layers_to_plot = [e for e in self.config.layers if e.enabled] 147 | 148 | # Create files and keep track of names 149 | for layer in layers_to_plot: 150 | # gerber layers 151 | if isinstance(layer, GerberLayer): 152 | pctl.SetLayer(layer.id) 153 | pctl.OpenPlotfile(layer.id, PLOT_FORMAT_GERBER, layer.name) 154 | pctl.PlotLayer() 155 | pctl.ClosePlot() 156 | 157 | # rename 158 | srcPlot = pctl.GetPlotFileName() 159 | new_name = os.path.splitext(os.path.basename(self.file_name))[0] + layer.ext 160 | new_name = os.path.join(os.path.dirname(srcPlot),new_name) 161 | move(srcPlot, new_name) 162 | 163 | self.generated_files += [new_name] 164 | 165 | # drill layers 166 | if isinstance(layer, DrillLayer): 167 | project_name = os.path.splitext(os.path.split(self.file_name)[1])[0] 168 | 169 | drlPlot = os.path.join(self.output_folder,project_name + '-PTH.drl') 170 | 171 | new_name = os.path.splitext(os.path.basename(self.file_name))[0] + '-PTH' + layer.ext 172 | new_name = os.path.join(os.path.dirname(drlPlot),new_name) 173 | move(drlPlot, new_name) 174 | 175 | self.generated_files += [new_name] 176 | 177 | drlPlot = os.path.join(self.output_folder,project_name + '-NPTH.drl') 178 | new_name = os.path.splitext(os.path.basename(self.file_name))[0] + '-NPTH' + layer.ext 179 | new_name = os.path.join(os.path.dirname(drlPlot),new_name) 180 | move(drlPlot, new_name) 181 | 182 | self.generated_files += [new_name] 183 | 184 | return self.generated_files 185 | -------------------------------------------------------------------------------- /KiZip/dialog/__init__.py: -------------------------------------------------------------------------------- 1 | from .settings_dialog import SettingsDialog 2 | -------------------------------------------------------------------------------- /KiZip/dialog/dialog_base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ########################################################################### 4 | ## Python code generated with wxFormBuilder (version Oct 26 2018) 5 | ## http://www.wxformbuilder.org/ 6 | ## 7 | ## PLEASE DO *NOT* EDIT THIS FILE! 8 | ########################################################################### 9 | 10 | import wx 11 | import wx.xrc 12 | 13 | ########################################################################### 14 | ## Class SettingsDialogBase 15 | ########################################################################### 16 | 17 | class SettingsDialogBase ( wx.Dialog ): 18 | 19 | def __init__( self, parent ): 20 | wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"ZiKip", pos = wx.DefaultPosition, size = wx.Size( 320,400 ), style = wx.DEFAULT_DIALOG_STYLE|wx.BORDER_DEFAULT ) 21 | 22 | self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) 23 | 24 | 25 | self.Centre( wx.BOTH ) 26 | 27 | def __del__( self ): 28 | pass 29 | 30 | 31 | ########################################################################### 32 | ## Class SettingsDialogPanel 33 | ########################################################################### 34 | 35 | class SettingsDialogPanel ( wx.Panel ): 36 | 37 | def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 320,380 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): 38 | wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) 39 | 40 | bSizer21 = wx.BoxSizer( wx.VERTICAL ) 41 | 42 | self.notebook = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) 43 | 44 | bSizer21.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 5 ) 45 | 46 | bSizer22 = wx.BoxSizer( wx.HORIZONTAL ) 47 | 48 | self.m_button15 = wx.Button( self, wx.ID_ANY, u"Save Settings", wx.DefaultPosition, wx.DefaultSize, 0 ) 49 | bSizer22.Add( self.m_button15, 0, wx.ALIGN_LEFT|wx.ALL, 5 ) 50 | 51 | 52 | bSizer22.Add( ( 50, 0), 1, wx.EXPAND, 5 ) 53 | 54 | self.m_button13 = wx.Button( self, wx.ID_ANY, u"Generate Gerbers", wx.DefaultPosition, wx.DefaultSize, 0 ) 55 | bSizer22.Add( self.m_button13, 0, wx.ALL, 5 ) 56 | 57 | self.m_button14 = wx.Button( self, wx.ID_CANCEL, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0 ) 58 | bSizer22.Add( self.m_button14, 0, wx.ALL, 5 ) 59 | 60 | 61 | bSizer21.Add( bSizer22, 0, wx.ALIGN_CENTER, 5 ) 62 | 63 | 64 | self.SetSizer( bSizer21 ) 65 | self.Layout() 66 | 67 | # Connect Events 68 | self.m_button15.Bind( wx.EVT_BUTTON, self.OnSaveSettings ) 69 | self.m_button13.Bind( wx.EVT_BUTTON, self.OnGenerateGerbers ) 70 | self.m_button14.Bind( wx.EVT_BUTTON, self.OnExit ) 71 | 72 | def __del__( self ): 73 | pass 74 | 75 | 76 | # Virtual event handlers, overide them in your derived class 77 | def OnSaveSettings( self, event ): 78 | event.Skip() 79 | 80 | def OnGenerateGerbers( self, event ): 81 | event.Skip() 82 | 83 | def OnExit( self, event ): 84 | event.Skip() 85 | 86 | 87 | ########################################################################### 88 | ## Class GeneralSettingsPanelBase 89 | ########################################################################### 90 | 91 | class GeneralSettingsPanelBase ( wx.Panel ): 92 | 93 | def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 320,400 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): 94 | wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) 95 | 96 | bSizer23 = wx.BoxSizer( wx.VERTICAL ) 97 | 98 | sbSizer10 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Destination" ), wx.VERTICAL ) 99 | 100 | fgSizer2 = wx.FlexGridSizer( 0, 2, 0, 0 ) 101 | fgSizer2.AddGrowableCol( 1 ) 102 | fgSizer2.SetFlexibleDirection( wx.BOTH ) 103 | fgSizer2.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) 104 | 105 | self.m_staticText10 = wx.StaticText( sbSizer10.GetStaticBox(), wx.ID_ANY, u"Directory", wx.DefaultPosition, wx.DefaultSize, 0 ) 106 | self.m_staticText10.Wrap( -1 ) 107 | 108 | fgSizer2.Add( self.m_staticText10, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 109 | 110 | self.outputDirPicker = wx.DirPickerCtrl( sbSizer10.GetStaticBox(), wx.ID_ANY, wx.EmptyString, u"Select a folder", wx.DefaultPosition, wx.DefaultSize, wx.DIRP_SMALL|wx.DIRP_USE_TEXTCTRL ) 111 | fgSizer2.Add( self.outputDirPicker, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5 ) 112 | 113 | self.m_staticText11 = wx.StaticText( sbSizer10.GetStaticBox(), wx.ID_ANY, u"Name format", wx.DefaultPosition, wx.DefaultSize, 0 ) 114 | self.m_staticText11.Wrap( -1 ) 115 | 116 | fgSizer2.Add( self.m_staticText11, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 117 | 118 | bSizer24 = wx.BoxSizer( wx.HORIZONTAL ) 119 | 120 | self.fileNameFormatTextControl = wx.TextCtrl( sbSizer10.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) 121 | bSizer24.Add( self.fileNameFormatTextControl, 1, wx.ALL|wx.EXPAND, 5 ) 122 | 123 | self.m_button16 = wx.Button( sbSizer10.GetStaticBox(), wx.ID_ANY, u"?", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) 124 | bSizer24.Add( self.m_button16, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 4 ) 125 | 126 | 127 | fgSizer2.Add( bSizer24, 1, wx.EXPAND, 5 ) 128 | 129 | 130 | sbSizer10.Add( fgSizer2, 1, wx.EXPAND, 5 ) 131 | 132 | 133 | bSizer23.Add( sbSizer10, 0, wx.ALL|wx.EXPAND, 5 ) 134 | 135 | 136 | bSizer23.Add( ( 0, 0), 2, wx.EXPAND, 5 ) 137 | 138 | 139 | self.SetSizer( bSizer23 ) 140 | self.Layout() 141 | 142 | # Connect Events 143 | self.m_button16.Bind( wx.EVT_BUTTON, self.OnNameFormatHintClick ) 144 | 145 | def __del__( self ): 146 | pass 147 | 148 | 149 | # Virtual event handlers, overide them in your derived class 150 | def OnNameFormatHintClick( self, event ): 151 | event.Skip() 152 | 153 | 154 | ########################################################################### 155 | ## Class LayerSettingsPanelBase 156 | ########################################################################### 157 | 158 | class LayerSettingsPanelBase ( wx.Panel ): 159 | 160 | def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): 161 | wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) 162 | 163 | bSizer25 = wx.BoxSizer( wx.VERTICAL ) 164 | 165 | sbSizer11 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Layer Settings" ), wx.VERTICAL ) 166 | 167 | SizerList = wx.BoxSizer( wx.VERTICAL ) 168 | 169 | self.m_scrolledWindow1 = wx.ScrolledWindow( sbSizer11.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.VSCROLL ) 170 | self.m_scrolledWindow1.SetScrollRate( 5, 5 ) 171 | bSizer12 = wx.BoxSizer( wx.VERTICAL ) 172 | 173 | bSizer13 = wx.BoxSizer( wx.HORIZONTAL ) 174 | 175 | self.m_staticText8 = wx.StaticText( self.m_scrolledWindow1, wx.ID_ANY, u"Layer Name", wx.DefaultPosition, wx.DefaultSize, 0 ) 176 | self.m_staticText8.Wrap( -1 ) 177 | 178 | bSizer13.Add( self.m_staticText8, 1, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 179 | 180 | self.m_staticText9 = wx.StaticText( self.m_scrolledWindow1, wx.ID_ANY, u"Extension", wx.DefaultPosition, wx.DefaultSize, 0 ) 181 | self.m_staticText9.Wrap( -1 ) 182 | 183 | bSizer13.Add( self.m_staticText9, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 184 | 185 | 186 | bSizer12.Add( bSizer13, 0, wx.EXPAND, 5 ) 187 | 188 | self.LayerPanelArea = wx.Panel( self.m_scrolledWindow1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) 189 | bSizer12.Add( self.LayerPanelArea, 1, wx.ALL|wx.EXPAND, 5 ) 190 | 191 | 192 | self.m_scrolledWindow1.SetSizer( bSizer12 ) 193 | self.m_scrolledWindow1.Layout() 194 | bSizer12.Fit( self.m_scrolledWindow1 ) 195 | SizerList.Add( self.m_scrolledWindow1, 1, wx.ALL|wx.EXPAND, 5 ) 196 | 197 | 198 | sbSizer11.Add( SizerList, 1, wx.EXPAND, 5 ) 199 | 200 | 201 | bSizer25.Add( sbSizer11, 1, wx.ALL|wx.EXPAND, 5 ) 202 | 203 | 204 | self.SetSizer( bSizer25 ) 205 | self.Layout() 206 | 207 | def __del__( self ): 208 | pass 209 | 210 | 211 | ########################################################################### 212 | ## Class LayerItemPanelBase 213 | ########################################################################### 214 | 215 | class LayerItemPanelBase ( wx.Panel ): 216 | 217 | def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): 218 | wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) 219 | 220 | bSizer11 = wx.BoxSizer( wx.HORIZONTAL ) 221 | 222 | self.LayerEnabledCheckbox = wx.CheckBox( self, wx.ID_ANY, u"", wx.DefaultPosition, wx.DefaultSize, 0 ) 223 | bSizer11.Add( self.LayerEnabledCheckbox, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5 ) 224 | 225 | self.Extension = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) 226 | bSizer11.Add( self.Extension, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 227 | 228 | 229 | self.SetSizer( bSizer11 ) 230 | self.Layout() 231 | bSizer11.Fit( self ) 232 | 233 | def __del__( self ): 234 | pass 235 | 236 | 237 | ########################################################################### 238 | ## Class AddLayerDialog 239 | ########################################################################### 240 | 241 | class AddLayerDialog ( wx.Dialog ): 242 | 243 | def __init__( self, parent ): 244 | wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"Add Layer", pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_DIALOG_STYLE ) 245 | 246 | self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) 247 | 248 | bSizer28 = wx.BoxSizer( wx.VERTICAL ) 249 | 250 | fgSizer3 = wx.FlexGridSizer( 0, 2, 0, 0 ) 251 | fgSizer3.AddGrowableCol( 1 ) 252 | fgSizer3.SetFlexibleDirection( wx.BOTH ) 253 | fgSizer3.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) 254 | 255 | self.m_staticText12 = wx.StaticText( self, wx.ID_ANY, u"Layer", wx.DefaultPosition, wx.DefaultSize, 0 ) 256 | self.m_staticText12.Wrap( -1 ) 257 | 258 | fgSizer3.Add( self.m_staticText12, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 259 | 260 | self.LayerName = wx.StaticText( self, wx.ID_ANY, u"", wx.DefaultPosition, wx.DefaultSize, 0 ) 261 | self.LayerName.Wrap( -1 ) 262 | 263 | fgSizer3.Add( self.LayerName, 0, wx.ALL, 5 ) 264 | 265 | self.m_staticText13 = wx.StaticText( self, wx.ID_ANY, u"var0", wx.DefaultPosition, wx.DefaultSize, 0 ) 266 | self.m_staticText13.Wrap( -1 ) 267 | 268 | fgSizer3.Add( self.m_staticText13, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 269 | 270 | self.m_textCtrl4 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) 271 | fgSizer3.Add( self.m_textCtrl4, 0, wx.ALL|wx.EXPAND, 5 ) 272 | 273 | self.m_staticText14 = wx.StaticText( self, wx.ID_ANY, u"var1", wx.DefaultPosition, wx.DefaultSize, 0 ) 274 | self.m_staticText14.Wrap( -1 ) 275 | 276 | fgSizer3.Add( self.m_staticText14, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 277 | 278 | self.m_textCtrl5 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) 279 | fgSizer3.Add( self.m_textCtrl5, 0, wx.ALL|wx.EXPAND, 5 ) 280 | 281 | 282 | bSizer28.Add( fgSizer3, 0, wx.ALL|wx.EXPAND, 5 ) 283 | 284 | self.m_staticline1 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) 285 | bSizer28.Add( self.m_staticline1, 0, wx.EXPAND |wx.ALL, 5 ) 286 | 287 | bSizer29 = wx.BoxSizer( wx.HORIZONTAL ) 288 | 289 | self.m_button21 = wx.Button( self, wx.ID_OK, u"Confirm", wx.DefaultPosition, wx.DefaultSize, 0 ) 290 | bSizer29.Add( self.m_button21, 0, wx.ALL, 5 ) 291 | 292 | self.m_button20 = wx.Button( self, wx.ID_CANCEL, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0 ) 293 | bSizer29.Add( self.m_button20, 0, wx.ALL, 5 ) 294 | 295 | 296 | bSizer28.Add( bSizer29, 1, wx.EXPAND, 5 ) 297 | 298 | 299 | self.SetSizer( bSizer28 ) 300 | self.Layout() 301 | bSizer28.Fit( self ) 302 | 303 | self.Centre( wx.BOTH ) 304 | 305 | def __del__( self ): 306 | pass 307 | 308 | 309 | -------------------------------------------------------------------------------- /KiZip/dialog/settings_dialog.py: -------------------------------------------------------------------------------- 1 | """Subclass of settings_dialog, which is generated by wxFormBuilder.""" 2 | import os 3 | import re 4 | 5 | import wx 6 | 7 | from . import dialog_base 8 | 9 | def pop_error(msg): 10 | wx.MessageBox(msg, 'Error', wx.OK | wx.ICON_ERROR) 11 | 12 | 13 | class SettingsDialog(dialog_base.SettingsDialogBase): 14 | def __init__(self, config_save_func, 15 | file_name_format_hint, version): 16 | dialog_base.SettingsDialogBase.__init__(self, None) 17 | self.panel = SettingsDialogPanel( 18 | self, config_save_func, file_name_format_hint) 19 | best_size = self.panel.BestSize 20 | # hack for some gtk themes that incorrectly calculate best size 21 | best_size.IncBy(dx=0, dy=30) 22 | self.SetClientSize(best_size) 23 | self.SetTitle('KiZip %s' % version) 24 | 25 | # hack for new wxFormBuilder generating code incompatible with old wxPython 26 | # noinspection PyMethodOverriding 27 | def SetSizeHints(self, sz1, sz2): 28 | try: 29 | # wxPython 3 30 | self.SetSizeHintsSz(sz1, sz2) 31 | except TypeError: 32 | # wxPython 4 33 | super(SettingsDialog, self).SetSizeHints(sz1, sz2) 34 | 35 | 36 | 37 | # Implementing settings_dialog 38 | class SettingsDialogPanel(dialog_base.SettingsDialogPanel): 39 | def __init__(self, parent, config_save_func, 40 | file_name_format_hint): 41 | self.config_save_func = config_save_func 42 | dialog_base.SettingsDialogPanel.__init__(self, parent) 43 | self.general = GeneralSettingsPanel(self.notebook,file_name_format_hint) 44 | self.notebook.AddPage(self.general, "General") 45 | 46 | self.layers = LayerSettingsPanel(self.notebook) 47 | self.notebook.AddPage(self.layers, "Layers") 48 | 49 | def OnExit(self, event): 50 | self.GetParent().EndModal(wx.ID_CANCEL) 51 | 52 | def OnSaveSettings(self, event): 53 | self.config_save_func(self) 54 | 55 | def OnGenerateGerbers(self, event): 56 | self.GetParent().EndModal(wx.ID_OK) 57 | 58 | def finish_init(self): 59 | self.html.OnBoardRotationSlider(None) 60 | 61 | # Implementing GeneralSettingsPanelBase 62 | class GeneralSettingsPanel(dialog_base.GeneralSettingsPanelBase): 63 | 64 | def __init__(self, parent, file_name_format_hint): 65 | dialog_base.GeneralSettingsPanelBase.__init__(self, parent) 66 | self.file_name_format_hint = file_name_format_hint 67 | 68 | def OnNameFormatHintClick(self, event): 69 | wx.MessageBox(self.file_name_format_hint, 'File name format help', 70 | style=wx.ICON_NONE | wx.OK) 71 | 72 | 73 | # Implementing LayerSettingsPanelBase 74 | class LayerSettingsPanel(dialog_base.LayerSettingsPanelBase): 75 | 76 | def __init__(self, parent): 77 | dialog_base.LayerSettingsPanelBase.__init__(self, parent) 78 | 79 | self.layers = [] 80 | self.bSizer = wx.BoxSizer( wx.VERTICAL ) 81 | self.LayerPanelArea.SetSizer(self.bSizer) 82 | 83 | 84 | def AddLayer(self, l): 85 | array = [p for p,lyr in list(self.layers) if lyr == l] 86 | pnl = next(array) if len(array) > 0 else None 87 | 88 | if pnl is None: 89 | # Create wx element and add to scroll box 90 | pnl = LayerItemPanelBase(self.LayerPanelArea, l.enabled, l.name, l.ext) 91 | self.bSizer.Add(pnl, 0, wx.EXPAND, 5 ) 92 | self.layers += [(pnl, l)] 93 | 94 | 95 | # Implementing LayerSettingsPanelBase 96 | class LayerItemPanelBase(dialog_base.LayerItemPanelBase): 97 | def __init__(self, parent, enabled, name, ext): 98 | dialog_base.LayerItemPanelBase.__init__(self, parent) 99 | 100 | self.LayerEnabledCheckbox.SetLabel(name) 101 | self.LayerEnabledCheckbox.SetValue(enabled) 102 | self.Extension.SetValue(ext) 103 | 104 | self.name = name 105 | 106 | def IsEnabled(self): 107 | return self.LayerEnabledCheckbox.IsChecked() 108 | 109 | def GetExtension(self): 110 | return self.Extension.GetValue() 111 | 112 | 113 | -------------------------------------------------------------------------------- /KiZip/dialog_test.py: -------------------------------------------------------------------------------- 1 | from dialog.settings_dialog import * 2 | 3 | class MyApp(wx.App): 4 | def OnInit(self): 5 | frame = SettingsDialog(lambda x: None, "Hi", 'test') 6 | if frame.ShowModal() == wx.ID_OK: 7 | print("Should generate bom") 8 | frame.Destroy() 9 | return True 10 | 11 | 12 | app = MyApp() 13 | app.MainLoop() 14 | 15 | print("Done") 16 | -------------------------------------------------------------------------------- /KiZip/errors.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class ExitCodes(): 5 | ERROR_PARSE = 3 6 | ERROR_FILE_NOT_FOUND = 4 7 | ERROR_NO_DISPLAY = 5 8 | 9 | 10 | class ParsingException(Exception): 11 | pass 12 | 13 | 14 | def exit_error(logger, code, err): 15 | logger.error(err) 16 | sys.exit(code) 17 | -------------------------------------------------------------------------------- /KiZip/generate_gerber_package.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from __future__ import absolute_import 3 | 4 | import argparse 5 | import os 6 | import sys 7 | 8 | import pcbnew 9 | 10 | # python 2 and 3 compatibility hack 11 | def to_utf(s): 12 | if isinstance(s, bytes): 13 | return s.decode('utf-8') 14 | else: 15 | return s 16 | 17 | 18 | if __name__ == "__main__": 19 | # Add ../ to the path 20 | # Works if this script is executed without installing the module 21 | script_dir = os.path.dirname(os.path.abspath(__file__)) 22 | sys.path.insert(0, os.path.dirname(script_dir)) 23 | os.environ['KIZIP_CLI_MODE'] = 'True' 24 | 25 | 26 | from KiZip.core import KiZip 27 | from KiZip.core.config import Config 28 | from KiZip.core.parser import Parser 29 | 30 | #from KiZip.ecad import get_parser_by_extension 31 | from KiZip.version import version 32 | from KiZip.errors import (ExitCodes, ParsingException, 33 | exit_error) 34 | 35 | create_wx_app = 'KIZIP_NO_DISPLAY' not in os.environ 36 | if create_wx_app: 37 | import wx 38 | app = wx.App() 39 | 40 | parser = argparse.ArgumentParser( 41 | description='KiCad KiZip plugin CLI.', 42 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 43 | parser.add_argument('file', 44 | type=lambda s: to_utf(s), 45 | help="KiCad PCB file") 46 | config = Config(version) 47 | config.add_options(parser, config.FILE_NAME_FORMAT_HINT) 48 | args = parser.parse_args() 49 | logger = KiZip.Logger(cli=True) 50 | if not os.path.isfile(args.file): 51 | exit_error(logger, ExitCodes.ERROR_FILE_NOT_FOUND, 52 | "File %s does not exist." % args.file) 53 | print("Loading %s" % args.file) 54 | 55 | config.rel_directory = os.path.dirname(args.file) 56 | board = pcbnew.LoadBoard(args.file) 57 | 58 | parser = Parser(args.file, config, logger, board) 59 | if args.show_dialog: 60 | if not create_wx_app: 61 | exit_error(logger, ExitCodes.ERROR_NO_DISPLAY, "Can not show dialog when " 62 | "KIZIP_NO_DISPLAY is set.") 63 | try: 64 | KiZip.run_with_dialog(parser, config, logger) 65 | except ParsingException as e: 66 | exit_error(logger, ExitCodes.ERROR_PARSE, e) 67 | else: 68 | config.set_from_args(args) 69 | try: 70 | KiZip.main(parser, config, logger) 71 | except ParsingException as e: 72 | exit_error(logger, ExitCodes.ERROR_PARSE, str(e)) 73 | -------------------------------------------------------------------------------- /KiZip/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregdavill/KiZip/24c9eced7143faa103993d5534968c0185007b2a/KiZip/icon.png -------------------------------------------------------------------------------- /KiZip/version.py: -------------------------------------------------------------------------------- 1 | # Update this when new version is tagged. 2 | LAST_TAG = '' 3 | 4 | import os 5 | plugin_path = os.path.realpath(os.path.dirname(__file__)) 6 | plugin_path if not os.path.islink(plugin_path) else os.readlink(plugin_path) 7 | 8 | def _get_git_version(): 9 | import os, subprocess 10 | try: 11 | git_version = subprocess.check_output( 12 | ['git', 'describe', '--tags', '--abbrev=4', '--dirty=-*'], 13 | cwd=plugin_path) 14 | return git_version.decode('utf-8') if isinstance(git_version, bytes) else git_version 15 | except subprocess.CalledProcessError as e: 16 | print('Git version check failed: ' + str(e)) 17 | except Exception as e: 18 | print('Git process cannot be launched: ' + str(e)) 19 | return None 20 | 21 | 22 | version = _get_git_version() or LAST_TAG 23 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Greg Davill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2-click gerber/drill export plugin for KiCad 2 | ![icon](icons/zip.png) 3 | 4 | This Action plugin makes the task of exporting and zipping up gerbers and drill files for PCB manufactures, quick and easy. With just two clicks from the pcbnew window. 5 | 6 | This plugin aims to be simple to install and use. Due to current limitation with the KiCad scripting engine it only covers gerber/drill files. 7 | For a more advanced plugin check out [KiBot](https://github.com/INTI-CMNB/KiBot) 8 | 9 | 10 | ## Licence and credits 11 | 12 | Plugin code licensed under MIT, see `LICENSE` for more info. 13 | 14 | KiCad Plugin code/structure from [Interactive HTML BOM](https://github.com/openscopeproject/InteractiveHtmlBom/) 15 | 16 | Icon by [Freepik](https://www.freepik.com/) from [Flaticon](https://www.flaticon.com) 17 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .KiZip import plugin -------------------------------------------------------------------------------- /icons/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregdavill/KiZip/24c9eced7143faa103993d5534968c0185007b2a/icons/zip.png -------------------------------------------------------------------------------- /settings_dialog.fbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ; 6 | Python 7 | 1 8 | source_name 9 | 0 10 | 0 11 | res 12 | UTF-8 13 | connect 14 | dialog_base 15 | 1000 16 | none 17 | 1 18 | 0 19 | kiZip 20 | 21 | ./KiZip/dialog 22 | 23 | 1 24 | 1 25 | 1 26 | 1 27 | UI 28 | 0 29 | 0 30 | 31 | 0 32 | wxAUI_MGR_DEFAULT 33 | 34 | wxBOTH 35 | 36 | 1 37 | 1 38 | impl_virtual 39 | 40 | 41 | 42 | 0 43 | wxID_ANY 44 | 45 | 46 | SettingsDialogBase 47 | 48 | 320,400 49 | wxDEFAULT_DIALOG_STYLE 50 | ; ; forward_declare 51 | ZiKip 52 | 53 | 54 | 55 | wxBORDER_DEFAULT 56 | 57 | 58 | 0 59 | wxAUI_MGR_DEFAULT 60 | 61 | 62 | 1 63 | 1 64 | impl_virtual 65 | 66 | 67 | 0 68 | wxID_ANY 69 | 70 | 71 | SettingsDialogPanel 72 | 73 | 320,380 74 | ; ; forward_declare 75 | 76 | 77 | 78 | wxTAB_TRAVERSAL 79 | 80 | 81 | bSizer21 82 | wxVERTICAL 83 | none 84 | 85 | 5 86 | wxEXPAND | wxALL 87 | 1 88 | 89 | 1 90 | 1 91 | 1 92 | 1 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 1 102 | 0 103 | 1 104 | 105 | 1 106 | 0 107 | Dock 108 | 0 109 | Left 110 | 1 111 | 112 | 1 113 | 114 | 0 115 | 0 116 | wxID_ANY 117 | 118 | 0 119 | 120 | 121 | 0 122 | 123 | 1 124 | notebook 125 | 1 126 | 127 | 128 | protected 129 | 1 130 | 131 | Resizable 132 | 1 133 | 134 | 135 | ; ; forward_declare 136 | 0 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 5 145 | wxALIGN_CENTER 146 | 0 147 | 148 | 149 | bSizer22 150 | wxHORIZONTAL 151 | none 152 | 153 | 5 154 | wxALIGN_LEFT|wxALL 155 | 0 156 | 157 | 1 158 | 1 159 | 1 160 | 1 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 1 170 | 0 171 | 1 172 | 173 | 1 174 | 175 | 0 176 | 0 177 | 178 | Dock 179 | 0 180 | Left 181 | 1 182 | 183 | 1 184 | 185 | 186 | 0 187 | 0 188 | wxID_ANY 189 | Save Settings 190 | 191 | 0 192 | 193 | 0 194 | 195 | 196 | 0 197 | 198 | 1 199 | m_button15 200 | 1 201 | 202 | 203 | protected 204 | 1 205 | 206 | 207 | 208 | Resizable 209 | 1 210 | 211 | 212 | ; ; forward_declare 213 | 0 214 | 215 | 216 | wxFILTER_NONE 217 | wxDefaultValidator 218 | 219 | 220 | 221 | 222 | OnSaveSettings 223 | 224 | 225 | 226 | 5 227 | wxEXPAND 228 | 1 229 | 230 | 0 231 | protected 232 | 50 233 | 234 | 235 | 236 | 5 237 | wxALL 238 | 0 239 | 240 | 1 241 | 1 242 | 1 243 | 1 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 1 253 | 0 254 | 1 255 | 256 | 1 257 | 258 | 0 259 | 0 260 | 261 | Dock 262 | 0 263 | Left 264 | 1 265 | 266 | 1 267 | 268 | 269 | 0 270 | 0 271 | wxID_ANY 272 | Generate Gerbers 273 | 274 | 0 275 | 276 | 0 277 | 278 | 279 | 0 280 | 281 | 1 282 | m_button13 283 | 1 284 | 285 | 286 | protected 287 | 1 288 | 289 | 290 | 291 | Resizable 292 | 1 293 | 294 | 295 | ; ; forward_declare 296 | 0 297 | 298 | 299 | wxFILTER_NONE 300 | wxDefaultValidator 301 | 302 | 303 | 304 | 305 | OnGenerateGerbers 306 | 307 | 308 | 309 | 5 310 | wxALL 311 | 0 312 | 313 | 1 314 | 1 315 | 1 316 | 1 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 1 326 | 0 327 | 1 328 | 329 | 1 330 | 331 | 0 332 | 0 333 | 334 | Dock 335 | 0 336 | Left 337 | 1 338 | 339 | 1 340 | 341 | 342 | 0 343 | 0 344 | wxID_CANCEL 345 | Cancel 346 | 347 | 0 348 | 349 | 0 350 | 351 | 352 | 0 353 | 354 | 1 355 | m_button14 356 | 1 357 | 358 | 359 | protected 360 | 1 361 | 362 | 363 | 364 | Resizable 365 | 1 366 | 367 | 368 | ; ; forward_declare 369 | 0 370 | 371 | 372 | wxFILTER_NONE 373 | wxDefaultValidator 374 | 375 | 376 | 377 | 378 | OnExit 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 0 387 | wxAUI_MGR_DEFAULT 388 | 389 | 390 | 1 391 | 1 392 | impl_virtual 393 | 394 | 395 | 0 396 | wxID_ANY 397 | 398 | 399 | GeneralSettingsPanelBase 400 | 401 | 320,400 402 | ; ; forward_declare 403 | 404 | 405 | 406 | wxTAB_TRAVERSAL 407 | 408 | 409 | bSizer23 410 | wxVERTICAL 411 | none 412 | 413 | 5 414 | wxALL|wxEXPAND 415 | 0 416 | 417 | wxID_ANY 418 | Destination 419 | 420 | sbSizer10 421 | wxVERTICAL 422 | 1 423 | none 424 | 425 | 5 426 | wxEXPAND 427 | 1 428 | 429 | 2 430 | wxBOTH 431 | 1 432 | 433 | 0 434 | 435 | fgSizer2 436 | wxFLEX_GROWMODE_SPECIFIED 437 | none 438 | 0 439 | 0 440 | 441 | 5 442 | wxALIGN_CENTER_VERTICAL|wxALL 443 | 0 444 | 445 | 1 446 | 1 447 | 1 448 | 1 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 1 457 | 0 458 | 1 459 | 460 | 1 461 | 0 462 | Dock 463 | 0 464 | Left 465 | 1 466 | 467 | 1 468 | 469 | 0 470 | 0 471 | wxID_ANY 472 | Directory 473 | 0 474 | 475 | 0 476 | 477 | 478 | 0 479 | 480 | 1 481 | m_staticText10 482 | 1 483 | 484 | 485 | protected 486 | 1 487 | 488 | Resizable 489 | 1 490 | 491 | 492 | ; ; forward_declare 493 | 0 494 | 495 | 496 | 497 | 498 | -1 499 | 500 | 501 | 502 | 5 503 | wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND 504 | 1 505 | 506 | 1 507 | 1 508 | 1 509 | 1 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 1 518 | 0 519 | 1 520 | 521 | 1 522 | 0 523 | Dock 524 | 0 525 | Left 526 | 1 527 | 528 | 1 529 | 530 | 0 531 | 0 532 | wxID_ANY 533 | 534 | 0 535 | 536 | Select a folder 537 | 538 | 0 539 | 540 | 1 541 | outputDirPicker 542 | 1 543 | 544 | 545 | protected 546 | 1 547 | 548 | Resizable 549 | 1 550 | 551 | wxDIRP_SMALL|wxDIRP_USE_TEXTCTRL 552 | ; ; forward_declare 553 | 0 554 | 555 | 556 | wxFILTER_NONE 557 | wxDefaultValidator 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 5 567 | wxALIGN_CENTER_VERTICAL|wxALL 568 | 0 569 | 570 | 1 571 | 1 572 | 1 573 | 1 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 1 582 | 0 583 | 1 584 | 585 | 1 586 | 0 587 | Dock 588 | 0 589 | Left 590 | 1 591 | 592 | 1 593 | 594 | 0 595 | 0 596 | wxID_ANY 597 | Name format 598 | 0 599 | 600 | 0 601 | 602 | 603 | 0 604 | 605 | 1 606 | m_staticText11 607 | 1 608 | 609 | 610 | protected 611 | 1 612 | 613 | Resizable 614 | 1 615 | 616 | 617 | ; ; forward_declare 618 | 0 619 | 620 | 621 | 622 | 623 | -1 624 | 625 | 626 | 627 | 5 628 | wxEXPAND 629 | 1 630 | 631 | 632 | bSizer24 633 | wxHORIZONTAL 634 | none 635 | 636 | 5 637 | wxALL|wxEXPAND 638 | 1 639 | 640 | 1 641 | 1 642 | 1 643 | 1 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 1 652 | 0 653 | 1 654 | 655 | 1 656 | 0 657 | Dock 658 | 0 659 | Left 660 | 1 661 | 662 | 1 663 | 664 | 0 665 | 0 666 | wxID_ANY 667 | 668 | 0 669 | 670 | 671 | 672 | 0 673 | 674 | 1 675 | fileNameFormatTextControl 676 | 1 677 | 678 | 679 | protected 680 | 1 681 | 682 | Resizable 683 | 1 684 | 685 | 686 | ; ; forward_declare 687 | 0 688 | 689 | 690 | wxFILTER_NONE 691 | wxDefaultValidator 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 4 701 | wxALIGN_CENTER_VERTICAL|wxALL 702 | 0 703 | 704 | 1 705 | 1 706 | 1 707 | 1 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 1 717 | 0 718 | 1 719 | 720 | 1 721 | 722 | 0 723 | 0 724 | 725 | Dock 726 | 0 727 | Left 728 | 1 729 | 730 | 1 731 | 732 | 733 | 0 734 | 0 735 | wxID_ANY 736 | ? 737 | 738 | 0 739 | 740 | 0 741 | 742 | 743 | 0 744 | 745 | 1 746 | m_button16 747 | 1 748 | 749 | 750 | protected 751 | 1 752 | 753 | 754 | 755 | Resizable 756 | 1 757 | 758 | wxBU_EXACTFIT 759 | ; ; forward_declare 760 | 0 761 | 762 | 763 | wxFILTER_NONE 764 | wxDefaultValidator 765 | 766 | 767 | 768 | 769 | OnNameFormatHintClick 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 5 780 | wxEXPAND 781 | 2 782 | 783 | 0 784 | protected 785 | 0 786 | 787 | 788 | 789 | 790 | 791 | 0 792 | wxAUI_MGR_DEFAULT 793 | 794 | 795 | 1 796 | 1 797 | impl_virtual 798 | 799 | 800 | 0 801 | wxID_ANY 802 | 803 | 804 | LayerSettingsPanelBase 805 | 806 | 500,300 807 | ; ; forward_declare 808 | 809 | 810 | 811 | wxTAB_TRAVERSAL 812 | 813 | 814 | bSizer25 815 | wxVERTICAL 816 | none 817 | 818 | 5 819 | wxALL|wxEXPAND 820 | 1 821 | 822 | wxID_ANY 823 | Layer Settings 824 | 825 | sbSizer11 826 | wxVERTICAL 827 | 1 828 | none 829 | 830 | 5 831 | wxEXPAND 832 | 1 833 | 834 | 835 | SizerList 836 | wxVERTICAL 837 | none 838 | 839 | 5 840 | wxALL|wxEXPAND 841 | 1 842 | 843 | 1 844 | 1 845 | 1 846 | 1 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 1 855 | 0 856 | 1 857 | 858 | 1 859 | 0 860 | Dock 861 | 0 862 | Left 863 | 1 864 | 865 | 1 866 | 867 | 0 868 | 0 869 | wxID_ANY 870 | 871 | 0 872 | 873 | 874 | 0 875 | 876 | 1 877 | m_scrolledWindow1 878 | 1 879 | 880 | 881 | protected 882 | 1 883 | 884 | Resizable 885 | 5 886 | 5 887 | 1 888 | 889 | ; ; forward_declare 890 | 0 891 | 892 | 893 | 894 | wxVSCROLL 895 | 896 | 897 | bSizer12 898 | wxVERTICAL 899 | none 900 | 901 | 5 902 | wxEXPAND 903 | 0 904 | 905 | 906 | bSizer13 907 | wxHORIZONTAL 908 | none 909 | 910 | 5 911 | wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL 912 | 1 913 | 914 | 1 915 | 1 916 | 1 917 | 1 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 1 926 | 0 927 | 1 928 | 929 | 1 930 | 0 931 | Dock 932 | 0 933 | Left 934 | 1 935 | 936 | 1 937 | 938 | 0 939 | 0 940 | wxID_ANY 941 | Layer Name 942 | 0 943 | 944 | 0 945 | 946 | 947 | 0 948 | 949 | 1 950 | m_staticText8 951 | 1 952 | 953 | 954 | protected 955 | 1 956 | 957 | Resizable 958 | 1 959 | 960 | 961 | ; ; forward_declare 962 | 0 963 | 964 | 965 | 966 | 967 | -1 968 | 969 | 970 | 971 | 5 972 | wxALIGN_CENTER_VERTICAL|wxALL 973 | 1 974 | 975 | 1 976 | 1 977 | 1 978 | 1 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 1 987 | 0 988 | 1 989 | 990 | 1 991 | 0 992 | Dock 993 | 0 994 | Left 995 | 1 996 | 997 | 1 998 | 999 | 0 1000 | 0 1001 | wxID_ANY 1002 | Extension 1003 | 0 1004 | 1005 | 0 1006 | 1007 | 1008 | 0 1009 | 1010 | 1 1011 | m_staticText9 1012 | 1 1013 | 1014 | 1015 | protected 1016 | 1 1017 | 1018 | Resizable 1019 | 1 1020 | 1021 | 1022 | ; ; forward_declare 1023 | 0 1024 | 1025 | 1026 | 1027 | 1028 | -1 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 5 1035 | wxALL|wxEXPAND 1036 | 1 1037 | 1038 | 1 1039 | 1 1040 | 1 1041 | 1 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1 1050 | 0 1051 | 1 1052 | 1053 | 1 1054 | 0 1055 | Dock 1056 | 0 1057 | Left 1058 | 1 1059 | 1060 | 1 1061 | 1062 | 0 1063 | 0 1064 | wxID_ANY 1065 | 1066 | 0 1067 | 1068 | 1069 | 0 1070 | 1071 | 1 1072 | LayerPanelArea 1073 | 1 1074 | 1075 | 1076 | protected 1077 | 1 1078 | 1079 | Resizable 1080 | 1 1081 | 1082 | ; ; forward_declare 1083 | 0 1084 | 1085 | 1086 | 1087 | wxTAB_TRAVERSAL 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 0 1101 | wxAUI_MGR_DEFAULT 1102 | 1103 | 1104 | 1 1105 | 1 1106 | impl_virtual 1107 | 1108 | 1109 | 0 1110 | wxID_ANY 1111 | 1112 | 1113 | LayerItemPanelBase 1114 | 1115 | -1,-1 1116 | ; ; forward_declare 1117 | 1118 | 1119 | 1120 | wxTAB_TRAVERSAL 1121 | 1122 | 1123 | bSizer11 1124 | wxHORIZONTAL 1125 | none 1126 | 1127 | 5 1128 | wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND 1129 | 1 1130 | 1131 | 1 1132 | 1 1133 | 1 1134 | 1 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1 1143 | 0 1144 | 0 1145 | 1 1146 | 1147 | 1 1148 | 0 1149 | Dock 1150 | 0 1151 | Left 1152 | 1 1153 | 1154 | 1 1155 | 1156 | 0 1157 | 0 1158 | wxID_ANY 1159 | <Back SolderMask> 1160 | 1161 | 0 1162 | 1163 | 1164 | 0 1165 | 1166 | 1 1167 | LayerEnabledCheckbox 1168 | 1 1169 | 1170 | 1171 | protected 1172 | 1 1173 | 1174 | Resizable 1175 | 1 1176 | 1177 | 1178 | ; ; forward_declare 1179 | 0 1180 | 1181 | 1182 | wxFILTER_NONE 1183 | wxDefaultValidator 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 5 1192 | wxALIGN_CENTER_VERTICAL|wxALL 1193 | 1 1194 | 1195 | 1 1196 | 1 1197 | 1 1198 | 1 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1 1207 | 0 1208 | 1 1209 | 1210 | 1 1211 | 0 1212 | Dock 1213 | 0 1214 | Left 1215 | 1 1216 | 1217 | 1 1218 | 1219 | 0 1220 | 0 1221 | wxID_ANY 1222 | 1223 | 0 1224 | 1225 | 1226 | 1227 | 0 1228 | 1229 | 1 1230 | Extension 1231 | 1 1232 | 1233 | 1234 | protected 1235 | 1 1236 | 1237 | Resizable 1238 | 1 1239 | 1240 | 1241 | ; ; forward_declare 1242 | 0 1243 | 1244 | 1245 | wxFILTER_NONE 1246 | wxDefaultValidator 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 0 1258 | wxAUI_MGR_DEFAULT 1259 | 1260 | wxBOTH 1261 | 1262 | 1 1263 | 1 1264 | impl_virtual 1265 | 1266 | 1267 | 1268 | 0 1269 | wxID_ANY 1270 | 1271 | 1272 | AddLayerDialog 1273 | 1274 | 1275 | wxDEFAULT_DIALOG_STYLE 1276 | ; ; forward_declare 1277 | Add Layer 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | bSizer28 1285 | wxVERTICAL 1286 | none 1287 | 1288 | 5 1289 | wxALL|wxEXPAND 1290 | 0 1291 | 1292 | 2 1293 | wxBOTH 1294 | 1 1295 | 1296 | 0 1297 | 1298 | fgSizer3 1299 | wxFLEX_GROWMODE_SPECIFIED 1300 | none 1301 | 0 1302 | 0 1303 | 1304 | 5 1305 | wxALIGN_CENTER_VERTICAL|wxALL 1306 | 0 1307 | 1308 | 1 1309 | 1 1310 | 1 1311 | 1 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1 1320 | 0 1321 | 1 1322 | 1323 | 1 1324 | 0 1325 | Dock 1326 | 0 1327 | Left 1328 | 1 1329 | 1330 | 1 1331 | 1332 | 0 1333 | 0 1334 | wxID_ANY 1335 | Layer 1336 | 0 1337 | 1338 | 0 1339 | 1340 | 1341 | 0 1342 | 1343 | 1 1344 | m_staticText12 1345 | 1 1346 | 1347 | 1348 | protected 1349 | 1 1350 | 1351 | Resizable 1352 | 1 1353 | 1354 | 1355 | ; ; forward_declare 1356 | 0 1357 | 1358 | 1359 | 1360 | 1361 | -1 1362 | 1363 | 1364 | 1365 | 5 1366 | wxALL 1367 | 0 1368 | 1369 | 1 1370 | 1 1371 | 1 1372 | 1 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1 1381 | 0 1382 | 1 1383 | 1384 | 1 1385 | 0 1386 | Dock 1387 | 0 1388 | Left 1389 | 1 1390 | 1391 | 1 1392 | 1393 | 0 1394 | 0 1395 | wxID_ANY 1396 | <Layer> 1397 | 0 1398 | 1399 | 0 1400 | 1401 | 1402 | 0 1403 | 1404 | 1 1405 | LayerName 1406 | 1 1407 | 1408 | 1409 | protected 1410 | 1 1411 | 1412 | Resizable 1413 | 1 1414 | 1415 | 1416 | ; ; forward_declare 1417 | 0 1418 | 1419 | 1420 | 1421 | 1422 | -1 1423 | 1424 | 1425 | 1426 | 5 1427 | wxALIGN_CENTER_VERTICAL|wxALL 1428 | 0 1429 | 1430 | 1 1431 | 1 1432 | 1 1433 | 1 1434 | 1435 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1 1442 | 0 1443 | 1 1444 | 1445 | 1 1446 | 0 1447 | Dock 1448 | 0 1449 | Left 1450 | 1 1451 | 1452 | 1 1453 | 1454 | 0 1455 | 0 1456 | wxID_ANY 1457 | var0 1458 | 0 1459 | 1460 | 0 1461 | 1462 | 1463 | 0 1464 | 1465 | 1 1466 | m_staticText13 1467 | 1 1468 | 1469 | 1470 | protected 1471 | 1 1472 | 1473 | Resizable 1474 | 1 1475 | 1476 | 1477 | ; ; forward_declare 1478 | 0 1479 | 1480 | 1481 | 1482 | 1483 | -1 1484 | 1485 | 1486 | 1487 | 5 1488 | wxALL|wxEXPAND 1489 | 0 1490 | 1491 | 1 1492 | 1 1493 | 1 1494 | 1 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1 1503 | 0 1504 | 1 1505 | 1506 | 1 1507 | 0 1508 | Dock 1509 | 0 1510 | Left 1511 | 1 1512 | 1513 | 1 1514 | 1515 | 0 1516 | 0 1517 | wxID_ANY 1518 | 1519 | 0 1520 | 1521 | 1522 | 1523 | 0 1524 | 1525 | 1 1526 | m_textCtrl4 1527 | 1 1528 | 1529 | 1530 | protected 1531 | 1 1532 | 1533 | Resizable 1534 | 1 1535 | 1536 | 1537 | ; ; forward_declare 1538 | 0 1539 | 1540 | 1541 | wxFILTER_NONE 1542 | wxDefaultValidator 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | 1551 | 5 1552 | wxALIGN_CENTER_VERTICAL|wxALL 1553 | 0 1554 | 1555 | 1 1556 | 1 1557 | 1 1558 | 1 1559 | 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1 1567 | 0 1568 | 1 1569 | 1570 | 1 1571 | 0 1572 | Dock 1573 | 0 1574 | Left 1575 | 1 1576 | 1577 | 1 1578 | 1579 | 0 1580 | 0 1581 | wxID_ANY 1582 | var1 1583 | 0 1584 | 1585 | 0 1586 | 1587 | 1588 | 0 1589 | 1590 | 1 1591 | m_staticText14 1592 | 1 1593 | 1594 | 1595 | protected 1596 | 1 1597 | 1598 | Resizable 1599 | 1 1600 | 1601 | 1602 | ; ; forward_declare 1603 | 0 1604 | 1605 | 1606 | 1607 | 1608 | -1 1609 | 1610 | 1611 | 1612 | 5 1613 | wxALL|wxEXPAND 1614 | 0 1615 | 1616 | 1 1617 | 1 1618 | 1 1619 | 1 1620 | 1621 | 1622 | 1623 | 1624 | 1625 | 1626 | 1627 | 1 1628 | 0 1629 | 1 1630 | 1631 | 1 1632 | 0 1633 | Dock 1634 | 0 1635 | Left 1636 | 1 1637 | 1638 | 1 1639 | 1640 | 0 1641 | 0 1642 | wxID_ANY 1643 | 1644 | 0 1645 | 1646 | 1647 | 1648 | 0 1649 | 1650 | 1 1651 | m_textCtrl5 1652 | 1 1653 | 1654 | 1655 | protected 1656 | 1 1657 | 1658 | Resizable 1659 | 1 1660 | 1661 | 1662 | ; ; forward_declare 1663 | 0 1664 | 1665 | 1666 | wxFILTER_NONE 1667 | wxDefaultValidator 1668 | 1669 | 1670 | 1671 | 1672 | 1673 | 1674 | 1675 | 1676 | 1677 | 1678 | 5 1679 | wxEXPAND | wxALL 1680 | 0 1681 | 1682 | 1 1683 | 1 1684 | 1 1685 | 1 1686 | 1687 | 1688 | 1689 | 1690 | 1691 | 1692 | 1693 | 1 1694 | 0 1695 | 1 1696 | 1697 | 1 1698 | 0 1699 | Dock 1700 | 0 1701 | Left 1702 | 1 1703 | 1704 | 1 1705 | 1706 | 0 1707 | 0 1708 | wxID_ANY 1709 | 1710 | 0 1711 | 1712 | 1713 | 0 1714 | 1715 | 1 1716 | m_staticline1 1717 | 1 1718 | 1719 | 1720 | protected 1721 | 1 1722 | 1723 | Resizable 1724 | 1 1725 | 1726 | wxLI_HORIZONTAL 1727 | ; ; forward_declare 1728 | 0 1729 | 1730 | 1731 | 1732 | 1733 | 1734 | 1735 | 1736 | 5 1737 | wxEXPAND 1738 | 1 1739 | 1740 | 1741 | bSizer29 1742 | wxHORIZONTAL 1743 | none 1744 | 1745 | 5 1746 | wxALL 1747 | 0 1748 | 1749 | 1 1750 | 1 1751 | 1 1752 | 1 1753 | 1754 | 1755 | 1756 | 1757 | 1758 | 1759 | 1760 | 1761 | 1 1762 | 0 1763 | 1 1764 | 1765 | 1 1766 | 1767 | 0 1768 | 0 1769 | 1770 | Dock 1771 | 0 1772 | Left 1773 | 1 1774 | 1775 | 1 1776 | 1777 | 1778 | 0 1779 | 0 1780 | wxID_OK 1781 | Confirm 1782 | 1783 | 0 1784 | 1785 | 0 1786 | 1787 | 1788 | 0 1789 | 1790 | 1 1791 | m_button21 1792 | 1 1793 | 1794 | 1795 | protected 1796 | 1 1797 | 1798 | 1799 | 1800 | Resizable 1801 | 1 1802 | 1803 | 1804 | ; ; forward_declare 1805 | 0 1806 | 1807 | 1808 | wxFILTER_NONE 1809 | wxDefaultValidator 1810 | 1811 | 1812 | 1813 | 1814 | 1815 | 1816 | 1817 | 5 1818 | wxALL 1819 | 0 1820 | 1821 | 1 1822 | 1 1823 | 1 1824 | 1 1825 | 1826 | 1827 | 1828 | 1829 | 1830 | 1831 | 1832 | 1833 | 1 1834 | 0 1835 | 1 1836 | 1837 | 1 1838 | 1839 | 0 1840 | 0 1841 | 1842 | Dock 1843 | 0 1844 | Left 1845 | 1 1846 | 1847 | 1 1848 | 1849 | 1850 | 0 1851 | 0 1852 | wxID_CANCEL 1853 | Cancel 1854 | 1855 | 0 1856 | 1857 | 0 1858 | 1859 | 1860 | 0 1861 | 1862 | 1 1863 | m_button20 1864 | 1 1865 | 1866 | 1867 | protected 1868 | 1 1869 | 1870 | 1871 | 1872 | Resizable 1873 | 1 1874 | 1875 | 1876 | ; ; forward_declare 1877 | 0 1878 | 1879 | 1880 | wxFILTER_NONE 1881 | wxDefaultValidator 1882 | 1883 | 1884 | 1885 | 1886 | 1887 | 1888 | 1889 | 1890 | 1891 | 1892 | 1893 | 1894 | --------------------------------------------------------------------------------