diff --git a/.gitignore b/.gitignore index 9001e8f..a8600b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .venv dist/ -.DS_Store \ No newline at end of file +.DS_Store +*.pyc \ No newline at end of file diff --git a/README.md b/README.md index 5daaa96..01bcfa5 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,35 @@ _The fields will be queried in the order denoted above._ _The fields will be queried in the order denoted above._ +## CLI-Usage +The plugin can also be used via the linux commandline. This can be particularly useful if you need to embed the plugin into an automation pipeline or environment. +The plugin can be called with the command below: +``` +python3 -m plugins.cli -p /myProject/myBoard.kicad_pcb +``` + +The all options from the GUI are also available via the cli interface: +``` +python3 -m plugins.cli -h + +usage: Fabrication Toolkit [-h] --path PATH [--additionalLayers ADDITIONALLAYERS] [--user1VCut] [--user2AltVCut] [--autoTranslate] [--autoFill] + [--excludeDNP] [--allActiveLayers] [--openBrowser] + +Generates JLCPCB production files from a KiCAD board file + +options: + -h, --help show this help message and exit + --path PATH, -p PATH Path to KiCAD board file + --additionalLayers ADDITIONALLAYERS, -aL ADDITIONALLAYERS Additional layers(comma-separated) + --user1VCut, -u1 Set User.1 as V-Cut layer + --user2AltVCut, -u2 Use User.2 for alternative Edge-Cut layer + --autoTranslate, -t Apply automatic position/rotation translations + --autoFill, -f Apply automatic fill for all zones + --excludeDNP, -e Exclude DNP components from BOM + --allActiveLayers, -aaL Export all active layers instead of only commonly used ones + --openBrowser, -b Open webbrowser with directory file overview after generation +``` + ## Author diff --git a/plugins/cli.py b/plugins/cli.py new file mode 100644 index 0000000..3b3dd17 --- /dev/null +++ b/plugins/cli.py @@ -0,0 +1,36 @@ +import argparse as ap + +from .thread import ProcessThread +from .options import * + + +if __name__ == '__main__': + parser = ap.ArgumentParser(prog="Fabrication Toolkit", + description="Generates JLCPCB production files from a KiCAD board file") + + parser.add_argument("--path", "-p", type=str, help="Path to KiCAD board file", required=True) + parser.add_argument("--additionalLayers", "-aL", type=str, help="Additional layers(comma-separated)") + parser.add_argument("--user1VCut", "-u1", action="store_true", help="Set User.1 as V-Cut layer") + parser.add_argument("--user2AltVCut", "-u2", action="store_true", help="Use User.2 for alternative Edge-Cut layer") + parser.add_argument("--autoTranslate", "-t", action="store_true", help="Apply automatic position/rotation translations") + parser.add_argument("--autoFill", "-f", action="store_true", help="Apply automatic fill for all zones") + parser.add_argument("--excludeDNP", "-e", action="store_true", help="Exclude DNP components from BOM") + parser.add_argument("--allActiveLayers", "-aaL",action="store_true", help="Export all active layers instead of only commonly used ones") + parser.add_argument("--openBrowser", "-b", action="store_true", help="Open webbrowser with directory file overview after generation") + args = parser.parse_args() + + options = dict() + options[AUTO_TRANSLATE_OPT] = args.autoTranslate + options[AUTO_FILL_OPT] = args.autoFill + options[EXCLUDE_DNP_OPT] = args.excludeDNP + options[EXTEND_EDGE_CUT_OPT] = args.user1VCut + options[ALTERNATIVE_EDGE_CUT_OPT] = args.user2AltVCut + options[ALL_ACTIVE_LAYERS_OPT] = args.allActiveLayers + options[EXTRA_LAYERS] = args.additionalLayers + + openBrowser = args.openBrowser + + + path = args.path + + ProcessThread(wx=None, cli=path, options=options, openBrowser=openBrowser) \ No newline at end of file diff --git a/plugins/process.py b/plugins/process.py index 91a4c7f..a7a6561 100644 --- a/plugins/process.py +++ b/plugins/process.py @@ -18,8 +18,12 @@ from .config import * class ProcessManager: - def __init__(self): - self.board = pcbnew.GetBoard() + def __init__(self, board = None): + # if no board is already loaded by cli mode getBoard from kicad environment + if board is None: + self.board = pcbnew.GetBoard() + else: + self.board = board self.bom = [] self.components = [] self.__rotation_db = self.__read_rotation_db() diff --git a/plugins/thread.py b/plugins/thread.py index 6a27cd8..14769d1 100644 --- a/plugins/thread.py +++ b/plugins/thread.py @@ -5,20 +5,38 @@ import tempfile import webbrowser import datetime +import logging from threading import Thread from .events import StatusEvent from .process import ProcessManager from .config import * from .options import * +from .utils import print_cli_progress_bar class ProcessThread(Thread): - def __init__(self, wx, options): + def __init__(self, wx, options, cli = None, openBrowser = True): Thread.__init__(self) - self.process_manager = ProcessManager() + # prevent use of cli and grapgical mode at the same time + if (wx is None and cli is None) or (wx is not None and cli is not None): + logging.error("Specify either graphical or cli use!") + return + + if cli is not None: + try: + self.board = pcbnew.LoadBoard(cli) + except Exception as e: + logging.error("Fabrication Toolkit - Error" + str(e)) + return + else: + self.board = None + + self.process_manager = ProcessManager(self.board) self.wx = wx + self.cli = cli self.options = options + self.openBrowser = openBrowser self.start() def run(self): @@ -70,7 +88,10 @@ def run(self): shutil.rmtree(temp_dir_gerber) temp_file = os.path.join(temp_dir, os.path.basename(temp_file)) except Exception as e: - wx.MessageBox(str(e), "Fabrication Toolkit - Error", wx.OK | wx.ICON_ERROR) + if self.wx is None: + logging.error("Fabrication Toolkit - Error" + str(e)) + else: + wx.MessageBox(str(e), "Fabrication Toolkit - Error", wx.OK | wx.ICON_ERROR) self.progress(-1) return @@ -118,12 +139,20 @@ def run(self): # copy to & open output dir try: shutil.copytree(temp_dir, output_path, dirs_exist_ok=True) - webbrowser.open("file://%s" % (output_path)) + if self.openBrowser: + webbrowser.open("file://%s" % (output_path)) shutil.rmtree(temp_dir) - except Exception as e: - webbrowser.open("file://%s" % (temp_dir)) + except Exception as e: + if self.openBrowser: + webbrowser.open("file://%s" % (temp_dir)) - self.progress(-1) + if self.wx is None: + self.progress(100) + else: + self.progress(-1) def progress(self, percent): - wx.PostEvent(self.wx, StatusEvent(percent)) + if self.wx is None: + print_cli_progress_bar(percent, prefix = 'Progress:', suffix = 'Complete', length = 50) + else: + wx.PostEvent(self.wx, StatusEvent(percent)) diff --git a/plugins/utils.py b/plugins/utils.py index aacd88f..c601c59 100644 --- a/plugins/utils.py +++ b/plugins/utils.py @@ -77,3 +77,25 @@ def get_layer_names(board, active_only=True): """Returns a list of (active) layer names of the current board""" plotPlan = get_plot_plan(board, active_only) return [layer_info[0] for layer_info in plotPlan] + +def print_cli_progress_bar(percent, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█', printEnd = "\r"): + """ + Call in a loop to create terminal progress bar string + @params: + percent - Required : current percentage (Int) + prefix - Optional : prefix string (Str) + suffix - Optional : suffix string (Str) + decimals - Optional : positive number of decimals in percent complete (Int) + length - Optional : character length of bar (Int) + fill - Optional : bar fill character (Str) + printEnd - Optional : end character (e.g. "\r", "\r\n") (Str) + """ + if percent == -1: + percent = 0 + filledLength = int(length * (percent / 100 )) + bar = fill * filledLength + '-' * (length - filledLength) + percent2dec = "%.2f" % percent + print(f'\r{prefix} |{bar}| {percent2dec}% {suffix}', end = printEnd) + # Print New Line on Complete + if percent == 100: + print() \ No newline at end of file