xenua
1 year ago
commit
e87050418a
5 changed files with 209 additions and 0 deletions
@ -0,0 +1,32 @@
@@ -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! <params> |
||||
<text> |
||||
``` |
||||
|
||||
where `<params>` is a space separated list of options and `<text>` is the text inserted into the code |
||||
|
||||
possible params: |
||||
|
||||
| param | what it does | |
||||
|------------------:|:-----------------------------------------| |
||||
| dot_size=<int> | size of a single pixel, default 0.5mm | |
||||
| border_size=<int> | 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 |
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
from .qrgen_action import QrgenPluginAction |
||||
QrgenPluginAction().register() |
After Width: | Height: | Size: 200 B |
@ -0,0 +1,174 @@
@@ -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() |
||||
|
||||
|
Loading…
Reference in new issue