This tutorial is a Dev.to–style, step-by-step walkthrough of the QRForge PRO v2.0.0 desktop application.
We’ll progressively build a professional QR code design studio using:
🐍 Python
🖼 PySide6 (Qt for Python)
🔳 qrcode
🧠 GraphicsScene / GraphicsView
📤 PNG, SVG, and PDF exports
👉 Full source code: https://github.com/rogers-cyber/QRForgePRO
0️⃣ What You’ll Build
By the end, you’ll have an app that can:
Generate QR codes from Text, URLs, and Wi‑Fi credentials
Customize colors, backgrounds, transparency
Move, rotate, and scale QRs visually
Export print‑ready PNG, SVG, and PDF files
1️⃣ Project Setup
Install dependencies
pip install PySide6 qrcode pillow requests
File structure
qrforge/
├─ main.py
├─ logo.ico
2️⃣ Importing What We Need
We start by importing system utilities, Qt widgets, and QR helpers.
import sys, os, math, requests
import qrcode
from PIL import Image
Qt imports
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from PySide6.QtSvg import QSvgGenerator
from PySide6.QtGui import QPdfWriter, QPageSize
👉 These give us:
Windows & dialogs
Vector drawing
Scene-based rendering
SVG/PDF export
3️⃣ App Metadata
Centralized metadata keeps things clean and reusable.
APP_NAME = "QRForge PRO"
APP_VERSION = "2.0.0"
APP_AUTHOR = "Mate Technologies"
APP_URL = "https://matetools.gumroad.com"
4️⃣ Utility Functions
Resource loader (for PyInstaller compatibility)
def resource_path(name):
base = getattr(sys, "_MEIPASS", os.path.dirname(__file__))
return os.path.join(base, name)
URL shortener (TinyURL API)
def shorten_url(url):
try:
r = requests.get(f"https://tinyurl.com/api-create.php?url={url}", timeout=5)
return r.text if r.status_code == 200 else url
except:
return url
Wi‑Fi QR payload format
def wifi_payload(ssid, pwd, enc):
return f"WIFI:S:{ssid};T:{enc if enc!='NONE' else ''};P:{pwd};;"
5️⃣ Creating a QR Code Graphics Item
Instead of rendering images directly, we create a custom QGraphicsItem. This allows:
Dragging
Rotation
Selection outlines
QRItem class
class QRItem(QGraphicsItem):
def __init__(self, data):
super().__init__()
self.data = data
self.size = 300
self.rotation_angle = 0
self.fill = QColor("black")
self.bg = QColor("white")
self.transparent = False
self.generate()
Generating the QR image
def generate(self):
qr = qrcode.QRCode(border=1)
qr.add_data(self.data)
qr.make(fit=True)
back = None if self.transparent else self.bg.name()
img = qr.make_image(
fill_color=self.fill.name(),
back_color=back
).convert("RGBA")
Converting PIL → QImage
self.qimage = QImage(
img.tobytes("raw", "RGBA"),
img.width,
img.height,
QImage.Format_RGBA8888
)
Painting & selection outline
def paint(self, p, *_):
p.save()
p.rotate(self.rotation_angle)
p.drawImage(self.boundingRect(), self.qimage)
p.restore()
if self.isSelected():
p.setPen(QPen(QColor("#00E676"), 2, Qt.DashLine))
p.drawRect(self.boundingRect())
6️⃣ Canvas: Zoom, Drag, Rotate
We extend QGraphicsView for interaction.
class Canvas(QGraphicsView):
def wheelEvent(self, e):
self.scale(1.15 if e.angleDelta().y() > 0 else 0.85,
1.15 if e.angleDelta().y() > 0 else 0.85)
ALT + mouse rotation
def mouseMoveEvent(self, e):
if self.rotating:
item = self.itemAt(e.position().toPoint())
if isinstance(item, QRItem):
dx = e.scenePosition().x() - item.scenePos().x()
dy = e.scenePosition().y() - item.scenePos().y()
item.rotation_angle = math.degrees(math.atan2(dy, dx))
item.update()
7️⃣ Main Window (QMainWindow)
This is where everything comes together.
class QRForgeStudio(QMainWindow):
def __init__(self):
super().__init__()
self.scene = QGraphicsScene(-5000, -5000, 10000, 10000)
self.canvas = Canvas(self.scene)
self.setCentralWidget(self.canvas)
8️⃣ Left Control Panel (DockWidget)
Mode selector
self.mode = QComboBox()
self.mode.addItems(["Text", "URL", "Wi‑Fi"])
Content input
self.text = QTextEdit()
self.text.setPlaceholderText("Enter content…")
Color & background controls
self.fill_btn = QPushButton("🎨 Fill Color")
self.bg_btn = QPushButton("🖌 Background")
self.trans = QCheckBox("Transparent Background")
9️⃣ Adding a QR to the Canvas
def add_qr(self):
mode = self.mode.currentText()
if mode == "Text":
data = self.text.toPlainText()
elif mode == "URL":
data = shorten_url(self.text.toPlainText())
else:
data = wifi_payload(self.ssid.text(), self.pwd.text(), self.enc.currentText())
item = QRItem(data)
self.scene.addItem(item)
item.setPos(0, 0)
🔟 Exporting (PNG, SVG, PDF)
PNG export (raster)
img = QImage(width, height, QImage.Format_ARGB32)
self.scene.render(painter)
img.save(path)
SVG export (vector)
svg = QSvgGenerator()
svg.setViewBox(rect)
self.scene.render(painter)
PDF export (print-ready)
pdf = QPdfWriter(path)
pdf.setPageSize(QPageSize.A4)
pdf.setResolution(300)
Each QR gets its own page.
1️⃣1️⃣ Entry Point
if __name__ == "__main__":
app = QApplication(sys.argv)
win = QRForgeStudio()
win.show()
sys.exit(app.exec())
🎉 Final Result
You now have:
A designer-friendly QR studio
Vector & print exports
Interactive canvas controls
Clean PySide6 architecture
👉 Full repository: https://github.com/rogers-cyber/QRForgePRO
🚀 What to Build Next
Preset templates
Logo embedding
Batch QR generation
PyInstaller packaging
Happy building! 🧡
I’ve created a Dev.to–style, beginner-friendly, step-by-step tutorial in the canvas that walks through QRForge PRO from zero to a complete app.
What this version does differently (on purpose)
✅ Breaks the project into small, digestible sections
✅ Uses short code blocks with explanations right after
✅ Explains why each Qt / QR concept exists, not just what
✅ Keeps it friendly for developers new to PySide6 + GraphicsView
✅ Links to the full source code clone without dumping unreadable walls of code
Perfect for:
Dev.to publishing
Medium / Hashnode cross-posts
Open-source project documentation
Teaching PySide6 visually

Top comments (0)