From e87050418ae0c6f5e092a1357872040b96cd98a1 Mon Sep 17 00:00:00 2001 From: xenua Date: Thu, 25 May 2023 12:59:49 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + README.md | 32 +++++++++ __init__.py | 2 + qrgen.png | Bin 0 -> 200 bytes qrgen_action.py | 174 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 __init__.py create mode 100644 qrgen.png create mode 100644 qrgen_action.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3820281 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# kicad-qrgen + +.. is a tool that looks for selected text elements with a +specific format, and outputs a qr code with the specified contents + +## usage + +add a text element on the layer you want, set the contents, +select that text (or multiple) and click the button + +## format + +``` +qrgen! + +``` + +where `` is a space separated list of options and `` is the text inserted into the code + +possible params: + +| param | what it does | +|------------------:|:-----------------------------------------| +| dot_size= | size of a single pixel, default 0.5mm | +| border_size= | num of pixels around the code, default 2 | +| invert | inverts the colors | + +## todo +- [ ] implement different shapes (rectangles) +- [ ] group the qr code automatically + +contributions very welcome, email me at `code at this site's base domain` or hit me up on fedi diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0944a68 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +from .qrgen_action import QrgenPluginAction +QrgenPluginAction().register() diff --git a/qrgen.png b/qrgen.png new file mode 100644 index 0000000000000000000000000000000000000000..a1dde802e8e5bc89e3f4b20545a9c9c1aab0ece4 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dx33?wJtG`|X@*aCb)T!Hle|NocXoPQU{Vk!yp z3;zHA#Mb-AfjrIvkH}&M25un`X1sK_?hjBf%G1R$gkxrMLd>Ca0jqz{J$N-_&cRY$ zf$3ewSC-YKC-5Yu|2Y3|zGHfyJ#(>%wX^TV61LR4e|gH+T{r&o{NA|(5);n{RLiW$ r54CH~_O)t$?Ydy!k}9@i615C0TP3Hh;)`bo+QQ)J>gTe~DWM4ftD#9) literal 0 HcmV?d00001 diff --git a/qrgen_action.py b/qrgen_action.py new file mode 100644 index 0000000..992f79e --- /dev/null +++ b/qrgen_action.py @@ -0,0 +1,174 @@ +from dataclasses import dataclass +from enum import Enum + +import pcbnew +from pcbnew import VECTOR2I + +import os +import qrcode +import wx + + +def relpath(p): + return os.path.join(os.path.dirname(__file__), f"./{p}") + + +class QrSpec: + @dataclass + class Params: + class Shape(Enum): + ROUND = "round" + SQUARE = "square" # todo: squares / rect + + dot_size: float + border_size: int + invert: bool + shape: Shape + + @classmethod + def default(cls): + return cls(dot_size=0.5, border_size=2, invert=False, shape=cls.Shape.ROUND) + + @classmethod + def from_header(cls, header): + _, h = header.split("qrgen!") + opts = h.strip().split(" ") + params = cls.default() + for opt in opts: + if "=" in opt: + inst, value = opt.split("=") + else: + inst = opt + value = None + + match inst: + case "dot_size": + params.dot_size = float(value) + case "border_size": + params.border_size = int(value) + case "invert": + params.invert = True + case "shape": + params.shape = cls.Shape(value) + + return params + + def __init__(self, raw_text: str): + self._raw = raw_text + self.header = self.content = self.params = None + self.init_success = False + self.init() + + def init(self): + self.header, self.content = self._raw.split("\n", maxsplit=1) + self.params = self.Params.from_header(self.header) + self.init_success = True + + def __repr__(self): + return f"QrSpec(header={repr(self.header)}, params={repr(self.params)}, content={repr(self.content)})" + + def as_line_segments(self): + q = qrcode.QRCode() + q.add_data(self.content) + q.border = self.params.border_size + data = q.get_matrix() + if self.params.invert: + data = [[not p for p in row] for row in data] + + return array_to_lines(data) + + +def array_to_lines(array: list[list[bool]]) -> tuple[list[list[tuple[int, int]]], list[list[tuple[int, int]]]]: + horiz = [line_to_segments(row) for row in array] + vert = [line_to_segments(col[::-1]) for col in list(zip(*array[::-1]))] # ????? + return horiz, vert + + +def line_to_segments(line: list[bool]) -> list[tuple[int, int]]: + in_seg = line[0] + out = [] + start = 0 + + for i, pix in enumerate(line): + if pix and not in_seg: + start = i + in_seg = True + elif not pix and in_seg: + out.append((start, i-1)) + in_seg = False + + if in_seg: + out.append((start, len(line)-1)) + + return out + + +def handle_exception(e): + dlg = wx.MessageDialog(None, f"error in processing: {e}") + dlg.ShowModal() + dlg.Destroy() + return + + +def add_line(parent, start, end, width, layer): + seg = pcbnew.PCB_SHAPE(parent) + seg.SetShape(pcbnew.SHAPE_T_SEGMENT) + seg.SetStart(start) + seg.SetEnd(end) + seg.SetWidth(int(width)) + seg.SetLayer(layer) + parent.Add(seg) + + +def qrgen(source_elem: pcbnew.PCB_TEXT): + raw = str(source_elem.GetText()) + layer = source_elem.GetLayer() + x = source_elem.GetX() + y = source_elem.GetY() + qr = QrSpec(raw) + board = pcbnew.GetBoard() + + h_lines, v_lines = qr.as_line_segments() + params = qr.params + dsize = int(params.dot_size * 1e6) + + all_segs = [] + + for i, line in enumerate(h_lines): + y_offset = i * dsize + for start, end in line: + all_segs.append((VECTOR2I(x + start * dsize, y + y_offset), + VECTOR2I(x + end * dsize, y + y_offset))) + + for i, col in enumerate(v_lines): + x_offset = i * dsize + for start, end in col: + all_segs.append((VECTOR2I(x + x_offset, y + start * dsize), + VECTOR2I(x + x_offset, y + end * dsize))) + + for a, b in all_segs: + add_line(board, a, b, dsize, layer) + + +class QrgenPluginAction(pcbnew.ActionPlugin): + def defaults(self): + self.name = "generate qr codes" + self.category = "Modify PCB" + self.description = "generate qr codes without leaving pcbnew" + self.show_toolbar_button = True + self.icon_file_name = relpath("qrgen.png") + + def Run(self): + qr_text: list[pcbnew.PCB_TEXT] = [text for text in pcbnew.GetCurrentSelection() + if isinstance(text, pcbnew.PCB_TEXT) + and str(text.GetText()).startswith("qrgen!")] + + for elem in qr_text: + try: + qrgen(elem) + except Exception as e: + handle_exception(e) + + pcbnew.Refresh() + + \ No newline at end of file