204 lines
6.5 KiB
Python
Executable File
204 lines
6.5 KiB
Python
Executable File
# aruco_svg_batch.py
|
|
from __future__ import annotations
|
|
import os
|
|
from typing import List, Tuple
|
|
|
|
OUT_DIR = "svgAruco"
|
|
FILL_COLOR = "#031740"
|
|
DICT_NAME = "DICT_5X5_250"
|
|
MARKER_SIZE_BITS = 5
|
|
BORDER_BITS = 1
|
|
SIDE_PIXELS_PER_MODULE = 8
|
|
START_INDEX = 0
|
|
PDF_NAME = "aruco_5x5_250_A4.pdf"
|
|
MARKER_SIZE_MM = 10.0
|
|
|
|
def _ensure_deps():
|
|
try:
|
|
import cv2
|
|
from cv2 import aruco
|
|
except Exception as e:
|
|
raise SystemExit("Install opencv-contrib-python: " + str(e))
|
|
try:
|
|
import reportlab
|
|
except Exception as e:
|
|
raise SystemExit("Install reportlab: " + str(e))
|
|
|
|
def _get_dictionary(dict_name: str):
|
|
from cv2 import aruco
|
|
if hasattr(aruco, "getPredefinedDictionary"):
|
|
return aruco.getPredefinedDictionary(getattr(aruco, dict_name))
|
|
return aruco.Dictionary_get(getattr(aruco, dict_name))
|
|
|
|
def _draw_marker(dictionary, marker_id: int, modules: int, spp: int):
|
|
from cv2 import aruco
|
|
side_px = modules * spp
|
|
if hasattr(aruco, "generateImageMarker"):
|
|
img = aruco.generateImageMarker(dictionary, marker_id, side_px, borderBits=BORDER_BITS)
|
|
else:
|
|
img = aruco.drawMarker(dictionary, marker_id, side_px, borderBits=BORDER_BITS)
|
|
return img
|
|
|
|
def _modules_from_image(img) -> List[Tuple[int, int]]:
|
|
import numpy as np
|
|
h, w = img.shape[:2]
|
|
modules = h // SIDE_PIXELS_PER_MODULE
|
|
step = SIDE_PIXELS_PER_MODULE
|
|
black_modules = []
|
|
bw = (img < 128).astype(np.uint8)
|
|
for r in range(modules):
|
|
rs = r * step
|
|
re = rs + step
|
|
for c in range(modules):
|
|
cs = c * step
|
|
ce = cs + step
|
|
block = bw[rs:re, cs:ce]
|
|
if block.mean() > 0.5:
|
|
black_modules.append((c, r))
|
|
return black_modules
|
|
|
|
def _svg_for_modules(black_modules: List[Tuple[int, int]], modules: int, fill: str) -> str:
|
|
parts = [
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {modules} {modules}" shape-rendering="crispEdges">',
|
|
f'<g fill="{fill}">'
|
|
]
|
|
for (x, y) in black_modules:
|
|
parts.append(f'<rect x="{x}" y="{y}" width="1" height="1"/>')
|
|
parts.append('</g></svg>')
|
|
return ''.join(parts)
|
|
|
|
# PDF helpers
|
|
|
|
def mm(v):
|
|
return v * 72.0 / 25.4
|
|
|
|
from reportlab.pdfgen import canvas as _c
|
|
from reportlab.lib.pagesizes import A4
|
|
from reportlab.lib.colors import HexColor, Color
|
|
from reportlab.pdfbase import pdfmetrics
|
|
|
|
def _hex_to_reportlab(c):
|
|
try:
|
|
return HexColor(c)
|
|
except Exception:
|
|
return Color(0, 0, 0)
|
|
|
|
def _layout_grid_vars(page_w, page_h):
|
|
MARKER_MM = MARKER_SIZE_MM
|
|
MARGIN_MM = 4.0
|
|
GUTTER_MM = 7.0
|
|
|
|
marker = mm(MARKER_MM)
|
|
margin = mm(MARGIN_MM)
|
|
gutter = mm(GUTTER_MM)
|
|
|
|
usable_w = page_w - 2 * margin
|
|
usable_h = page_h - 2 * margin
|
|
|
|
# Anzahl Marker berechnen, die reinpassen
|
|
cols = int((usable_w + gutter) // (marker + gutter))
|
|
rows = int((usable_h + gutter) // (marker + gutter))
|
|
|
|
return dict(
|
|
margin=margin,
|
|
gutter=gutter,
|
|
cols=cols,
|
|
rows=rows,
|
|
cell_w=marker,
|
|
cell_h=marker
|
|
)
|
|
|
|
def _draw_marker_into_cell(c, cell_x, cell_y, cell_w, cell_h,
|
|
black_modules, modules_per_side,
|
|
module_fill_hex, index_label: str):
|
|
# label area
|
|
EXTRA_BOTTOM_MM = 10.0
|
|
label_h = mm(4.5 + EXTRA_BOTTOM_MM)
|
|
|
|
marker_side = cell_w # statt min(...)
|
|
module_size = marker_side / float(modules_per_side)
|
|
|
|
|
|
mx = cell_x + (cell_w - marker_side) / 2.0
|
|
my = cell_y + (cell_h - label_h - marker_side) / 2.0 + label_h
|
|
|
|
c.saveState()
|
|
c.setFillColor(_hex_to_reportlab(module_fill_hex))
|
|
for (x, y) in black_modules:
|
|
rx = mx + x * module_size
|
|
#ry = my + y * module_size
|
|
ry = my + (modules_per_side - 1 - y) * module_size
|
|
c.rect(rx, ry, module_size, module_size, stroke=0, fill=1)
|
|
c.restoreState()
|
|
|
|
# orientation dot: 2mm inside
|
|
dot_d_mm = 0.4
|
|
edge_gap_mm = 0.4
|
|
r = mm(dot_d_mm) / 2.0
|
|
gap = mm(edge_gap_mm)
|
|
dot_cx = mx + gap + r
|
|
dot_cy = my + marker_side - (gap + r)
|
|
c.saveState()
|
|
c.setFillColor(_hex_to_reportlab('#510000'))
|
|
c.circle(dot_cx, dot_cy, r, stroke=0, fill=1)
|
|
c.restoreState()
|
|
|
|
# label: exact 2mm gap using font ascent
|
|
c.saveState()
|
|
font_name = 'Helvetica'
|
|
font_size = 7
|
|
c.setFont(font_name, font_size)
|
|
c.setFillColor(Color(0.7, 0.7, 0.7))
|
|
tx = mx + marker_side / 2.0
|
|
ascent = pdfmetrics.getAscent(font_name) * (font_size/1000.0)
|
|
baseline = my - mm(1.3) - ascent
|
|
c.drawCentredString(tx, baseline, index_label)
|
|
c.restoreState()
|
|
|
|
|
|
def build_a4_pdf(pdf_path: str = PDF_NAME):
|
|
_ensure_deps()
|
|
dictionary = _get_dictionary(DICT_NAME)
|
|
modules = MARKER_SIZE_BITS + 2 * BORDER_BITS
|
|
PAGE_W, PAGE_H = A4
|
|
c = _c.Canvas(pdf_path, pagesize=A4)
|
|
grid = _layout_grid_vars(PAGE_W, PAGE_H)
|
|
COLS, ROWS = grid['cols'], grid['rows']
|
|
per_page = COLS * ROWS
|
|
total = 250
|
|
for idx in range(total):
|
|
if idx % per_page == 0:
|
|
if idx > 0:
|
|
c.showPage()
|
|
i_on_page = idx % per_page
|
|
col = i_on_page % COLS
|
|
row = i_on_page // COLS
|
|
x = grid['margin'] + col * (grid['cell_w'] + grid['gutter'])
|
|
y = grid['margin'] + (grid['rows'] - 1 - row) * (grid['cell_h'] + grid['gutter'])
|
|
img = _draw_marker(dictionary, marker_id=idx, modules=modules, spp=SIDE_PIXELS_PER_MODULE)
|
|
black_modules = _modules_from_image(img)
|
|
label = f"{idx + START_INDEX:03d}"
|
|
_draw_marker_into_cell(c, x, y, grid['cell_w'], grid['cell_h'], black_modules, modules, FILL_COLOR, label)
|
|
c.save()
|
|
return pdf_path
|
|
|
|
|
|
def main():
|
|
_ensure_deps()
|
|
os.makedirs(OUT_DIR, exist_ok=True)
|
|
dictionary = _get_dictionary(DICT_NAME)
|
|
modules = MARKER_SIZE_BITS + 2 * BORDER_BITS
|
|
total = 250
|
|
for idx in range(total):
|
|
img = _draw_marker(dictionary, marker_id=idx, modules=modules, spp=SIDE_PIXELS_PER_MODULE)
|
|
black_modules = _modules_from_image(img)
|
|
svg = _svg_for_modules(black_modules, modules, FILL_COLOR)
|
|
fname = f"aruco_{idx + START_INDEX:03d}.svg"
|
|
with open(os.path.join(OUT_DIR, fname), 'w', encoding='utf-8') as f:
|
|
f.write(svg)
|
|
pdf_path = build_a4_pdf(PDF_NAME)
|
|
print('PDF saved to:', pdf_path)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|