๐Ÿงฑ QRForge PRO๋ฅผ PySide6์œผ๋กœ ๋นŒ๋“œํ•˜๊ธฐ (์ดˆ๋ณด์ž ์นœํ™” ๊ฐ€์ด๋“œ)

๋ฐœํ–‰: (2026๋…„ 1์›” 16์ผ ์˜คํ›„ 02:59 GMT+9)
7 min read
์›๋ฌธ: Dev.to

Iโ€™m happy to translate the article for you, but Iโ€™ll need the full text youโ€™d like translated. Could you please paste the content (or the portion you want translated) here? Iโ€™ll keep the source line exactly as you provided and translate the rest into Korean while preserving all formatting, markdown, and code blocks.

0๏ธโƒฃ ๋งŒ๋“ค๊ฒŒ ๋  ๊ฒƒ

์ด ํŠœํ† ๋ฆฌ์–ผ์„ ๋งˆ์น˜๋ฉด ๊ทธ๋ž˜ํ”ฝ ๋””์ž์ด๋„ˆ๊ฐ€ ์ž‘์—…ํ•˜๋“ฏ QR ์ฝ”๋“œ๋ฅผ ๋””์ž์ธํ•  ์ˆ˜ ์žˆ๋Š” ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ Qt ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

1๏ธโƒฃ ํ”„๋กœ์ ํŠธ ์„ค์ •

์˜์กด์„ฑ ์„ค์น˜

pip install PySide6 qrcode pillow requests

ํŒŒ์ผ ๊ตฌ์กฐ

qrforge/
โ”œโ”€ main.py
โ”œโ”€ logo.ico

2๏ธโƒฃ ํ•„์š”ํ•œ ๊ฒƒ ๊ฐ€์ ธ์˜ค๊ธฐ

# Standard library
import sys, os, math, requests

# QR helpers
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

์ด๋Ÿฌํ•œ import๋Š” ๋‹ค์Œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  • ์œˆ๋„์šฐ ๋ฐ ๋Œ€ํ™”์ƒ์ž โ€“ QtWidgets๋ฅผ ํ†ตํ•ด
  • ๋ฒกํ„ฐ ๊ทธ๋ฆฌ๊ธฐ โ€“ QtGui ๋ฐ QtSvg๋ฅผ ํ†ตํ•ด
  • ์”ฌ ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง โ€“ QGraphicsScene/View๋ฅผ ํ†ตํ•ด
  • SVG/PDF ๋‚ด๋ณด๋‚ด๊ธฐ โ€“ QSvgGenerator ๋ฐ QPdfWriter๋ฅผ ํ†ตํ•ด

3๏ธโƒฃ ์•ฑ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ

๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ค‘์•™ ์ง‘์ค‘ํ™”ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•˜๊ณ  ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

APP_NAME    = "QRForge PRO"
APP_VERSION = "2.0.0"
APP_AUTHOR  = "Mate Technologies"
APP_URL     = "https://matetools.gumroad.com"

4๏ธโƒฃ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜

# 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 Exception:
        return url

# Wiโ€‘Fi QR payload format
def wifi_payload(ssid, pwd, enc):
    # enc can be "WPA", "WEP", or "NONE"
    return f"WIFI:S:{ssid};T:{enc if enc!='NONE' else ''};P:{pwd};;"

5๏ธโƒฃ QR ์ฝ”๋“œ ๊ทธ๋ž˜ํ”ฝ ์•„์ดํ…œ ๋งŒ๋“ค๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ๋ Œ๋”๋งํ•˜๋Š” ๋Œ€์‹  ์ปค์Šคํ…€ QGraphicsItem์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ๋“œ๋ž˜๊ทธ, ํšŒ์ „, ์„ ํƒ ์œค๊ณฝ์„ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

QRItem ํด๋ž˜์Šค ๊ณจ๊ฒฉ

class QRItem(QGraphicsItem):
    def __init__(self, data):
        super().__init__()
        self.data = data
        self.size = 300                # default pixel size
        self.rotation_angle = 0
        self.fill = QColor("black")    # QR fill colour
        self.bg = QColor("white")      # background colour
        self.transparent = False
        self.generate()

QR ์ด๋ฏธ์ง€ ์ƒ์„ฑ

    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")

        # Convert PIL โ†’ QImage
        self.qimage = QImage(
            img.tobytes("raw", "RGBA"),
            img.width,
            img.height,
            QImage.Format_RGBA8888,
        )

ํŽ˜์ธํŒ… ๋ฐ ์„ ํƒ ์œค๊ณฝ์„ 

    def paint(self, painter, *_):
        painter.save()
        painter.rotate(self.rotation_angle)
        painter.drawImage(self.boundingRect(), self.qimage)
        painter.restore()

        if self.isSelected():
            painter.setPen(QPen(QColor("#00E676"), 2, Qt.DashLine))
            painter.drawRect(self.boundingRect())

6๏ธโƒฃ Canvas: ํ™•๋Œ€/์ถ•์†Œ, ๋“œ๋ž˜๊ทธ, ํšŒ์ „

class Canvas(QGraphicsView):
    def __init__(self, scene, parent=None):
        super().__init__(scene, parent)
        self.setRenderHint(QPainter.Antialiasing)
        self.rotating = False

    # ---------- Zoom ----------
    def wheelEvent(self, event):
        factor = 1.15 if event.angleDelta().y() > 0 else 0.85
        self.scale(factor, factor)

    # ---------- Rotation ----------
    def mousePressEvent(self, event):
        if event.modifiers() & Qt.AltModifier and event.button() == Qt.LeftButton:
            self.rotating = True
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        self.rotating = False
        super().mouseReleaseEvent(event)

    def mouseMoveEvent(self, event):
        if self.rotating:
            item = self.itemAt(event.position().toPoint())
            if isinstance(item, QRItem):
                dx = event.scenePosition().x() - item.scenePos().x()
                dy = event.scenePosition().y() - item.scenePos().y()
                item.rotation_angle = math.degrees(math.atan2(dy, dx))
                item.update()
        else:
            super().mouseMoveEvent(event)

7๏ธโƒฃ ๋ฉ”์ธ ์œˆ๋„์šฐ (QMainWindow)

๋ชจ๋“  UI ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์—ฌ๊ธฐ์—์„œ ์กฐํ•ฉ๋ฉ๋‹ˆ๋‹ค.

class QRForgeStudio(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")

        # Scene & canvas
        self.scene  = QGraphicsScene(-5000, -5000, 10000, 10000)
        self.canvas = Canvas(self.scene)
        self.setCentralWidget(self.canvas)

        # UI โ€“ left dock
        self._setup_left_dock()

8๏ธโƒฃ ์™ผ์ชฝ ์ œ์–ด ํŒจ๋„ (DockWidget)

๋ชจ๋“œ ์„ ํƒ๊ธฐ

self.mode = QComboBox()
self.mode.addItems(["Text", "URL", "Wiโ€‘Fi"])

๋‚ด์šฉ ์ž…๋ ฅ

self.text = QTextEdit()
self.text.setPlaceholderText("Enter contentโ€ฆ")

์ƒ‰์ƒ ๋ฐ ๋ฐฐ๊ฒฝ ์ œ์–ด

self.fill_btn = QPushButton("๐ŸŽจ Fill Colour")
self.bg_btn   = QPushButton("๐Ÿ–Œ Background")
self.trans    = QCheckBox("Transparent Background")

(์ถ”๊ฐ€ Wiโ€‘Fi ํ•„๋“œ โ€“ self.ssid, self.pwd, self.enc โ€“ ๋Š” ๋™์ผํ•œ ๋„ํฌ์— ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.)


9๏ธโƒฃ ์บ”๋ฒ„์Šค์— QR ์ถ”๊ฐ€

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:   # Wiโ€‘Fi
        data = wifi_payload(
            self.ssid.text(),
            self.pwd.text(),
            self.enc.currentText(),
        )

    item = QRItem(data)
    self.scene.addItem(item)
    item.setPos(0, 0)          # centre of the canvas

๐Ÿ”Ÿ ๋‚ด๋ณด๋‚ด๊ธฐ (PNG, SVG, PDF)

PNG ๋‚ด๋ณด๋‚ด๊ธฐ (๋ž˜์Šคํ„ฐ)

def export_png(self, path, width=2000, height=2000):
    img = QImage(width, height, QImage.Format_ARGB32)
    img.fill(Qt.transparent)

    painter = QPainter(img)
    self.scene.render(painter)
    painter.end()

    img.save(path)

SVG ๋‚ด๋ณด๋‚ด๊ธฐ (๋ฒกํ„ฐ)

def export_svg(self, path, rect=None):
    generator = QSvgGenerator()
    generator.setFileName(path)
    generator.setSize(QSize(2000, 2000))
    generator.setViewBox(rect or self.scene.sceneRect())
    generator.setTitle(APP_NAME)
    generator.setDescription("QRForge PRO SVG export")

    painter = QPainter(generator)
    self.scene.render(painter)
    painter.end()

PDF ๋‚ด๋ณด๋‚ด๊ธฐ (๋ฒกํ„ฐ)

def export_pdf(self, path, rect=None):
    writer = QPdfWriter(path)
    writer.setPageSize(QPageSize(QPageSize.A4))
    writer.setResolution(300)

    painter = QPainter(writer)
    self.scene.render(painter, target=writer.pageRect(), source=rect or self.scene.sceneRect())
    painter.end()

๐ŸŽ‰ ๋งˆ๋ฌด๋ฆฌ

์ด์ œ Python, PySide6, qrcode ๋กœ ๋งŒ๋“  ์™„์ „ํ•˜๊ณ  ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•œ QRโ€‘code ๋””์ž์ธ ์ŠคํŠœ๋””์˜ค๋ฅผ ๊ฐ–๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ž์œ ๋กญ๊ฒŒ:

  • ๋” ๋งŽ์€ ํšจ๊ณผ(๊ทธ๋ผ๋””์–ธํŠธ, ๊ทธ๋ฆผ์ž) ์ถ”๊ฐ€
  • QUndoStack ์œผ๋กœ undo/redo ๊ตฌํ˜„
  • PyInstaller ๋กœ ์•ฑ ํŒจํ‚ค์ง• (pyinstaller --onefile main.py)

์ฝ”๋”ฉ ์ฆ๊ฒ๊ฒŒ! ๐Ÿš€

QRForgeโ€ฏPRO โ€“ ์ดˆ๋ณด์ž ์นœํ™”์ ์ธ PySide6 QRโ€‘์ฝ”๋“œ ์ŠคํŠœ๋””์˜ค

PDF ๋‚ด๋ณด๋‚ด๊ธฐ (์ธ์‡„โ€‘์ค€๋น„)

pdf = QPdfWriter(path)
pdf.setPageSize(QPageSize.A4)
pdf.setResolution(300)

๊ฐ QR ์ฝ”๋“œ๊ฐ€ ๋ณ„๋„์˜ ํŽ˜์ด์ง€๋ฅผ ์ฐจ์ง€ํ•ฉ๋‹ˆ๋‹ค.


1๏ธโƒฃ ์ง„์ž…์ 

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:


๐Ÿš€ ๋‹ค์Œ์— ๋งŒ๋“ค ๊ฒƒ

  • ์‚ฌ์ „ ์„ค์ • ํ…œํ”Œ๋ฆฟ
  • ๋กœ๊ณ  ์‚ฝ์ž…
  • ๋ฐฐ์น˜ QR ์ƒ์„ฑ
  • PyInstaller ํŒจํ‚ค์ง•

์ฆ๊ฑฐ์šด ๊ฐœ๋ฐœ ๋˜์„ธ์š”! ๐Ÿงก


About This Tutorial

์ €๋Š” Dev.to ์Šคํƒ€์ผ์˜, ์ดˆ๋ณด์ž ์นœํ™”์ ์ธ ๋‹จ๊ณ„๋ณ„ ํŠœํ† ๋ฆฌ์–ผ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ํŠœํ† ๋ฆฌ์–ผ์€ QRForgeโ€ฏPRO๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์™„์ „ํ•œ ์•ฑ๊นŒ์ง€ ์ฐจ๊ทผ์ฐจ๊ทผ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค.

What this version does differently (on purpose)

  • โœ… ํ”„๋กœ์ ํŠธ๋ฅผ ์ž‘๊ณ  ์†Œํ™”ํ•˜๊ธฐ ์‰ฌ์šด ์„น์…˜์œผ๋กœ ๋‚˜๋ˆ•๋‹ˆ๋‹ค
  • โœ… ์งง์€ ์ฝ”๋“œ ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜๊ณ  ๋ฐ”๋กœ ๋’ค์— ์„ค๋ช…์„ ๋‹ฌ์•„ ๋‘ก๋‹ˆ๋‹ค
  • โœ… ๊ฐ Qt/QR ๊ฐœ๋…์ด ์™œ ์กด์žฌํ•˜๋Š”์ง€, ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ๋ชจ๋‘ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค
  • โœ… PySide6โ€ฏ+โ€ฏGraphicsView์— ์ต์ˆ™ํ•˜์ง€ ์•Š์€ ๊ฐœ๋ฐœ์ž์—๊ฒŒ๋„ ์นœ์ ˆํ•ฉ๋‹ˆ๋‹ค
  • โœ… ์ „์ฒด ์†Œ์Šค ์ฝ”๋“œ ๋ณต์ œ๋ณธ์— ๋Œ€ํ•œ ๋งํฌ๋ฅผ ์ œ๊ณตํ•˜๊ณ , ์ฝ๊ธฐ ํž˜๋“  ์ฝ”๋“œ ๋ฒฝ์„ ๋˜์ ธ๋†“์ง€ ์•Š์Šต๋‹ˆ๋‹ค

Perfect for

  • Dev.to ๊ฒŒ์‹œ
  • Medium / Hashnode ๊ต์ฐจ ๊ฒŒ์‹œ
  • ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ ๋ฌธ์„œํ™”
  • ์‹œ๊ฐ์ ์œผ๋กœ PySide6์„ ๊ฐ€๋ฅด์น  ๋•Œ

Preview

QR code design

Back to Blog

๊ด€๋ จ ๊ธ€

๋” ๋ณด๊ธฐ ยป

Python, Tkinter, MSS๋ฅผ ์‚ฌ์šฉํ•œ ํ™”๋ฉด ์บก์ฒ˜ ๋ฐ ์Šค์ฝ”ํ”„ ๋„๊ตฌ ๋งŒ๋“ค๊ธฐ

์‹ค์‹œ๊ฐ„ ํ™”๋ฉด ์บก์ฒ˜ GUI์™€ ์Šค์ฝ”ํ”„: ์ด ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™”๋ฉด์„ ์บก์ฒ˜ํ•˜๊ณ  ๋น„๋””์˜ค ์Šค์ฝ”ํ”„ ๋ฒกํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ž‘์€ GUI ๋„๊ตฌ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ์ง์ ‘ ๋งŒ๋“  GUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋ฐ์Šคํฌํ†ฑ์šฉ ์›Œ๋“ค์„ ๋งŒ๋“ค์—ˆ์–ด์š”!

๋ฐ์Šคํฌํ†ฑ์šฉ Wordle์„ ์ง์ ‘ ๋งŒ๋“  GUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ ์ปค๋ฒ„ ์ด๋ฏธ์ง€! https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format...

Python๊ณผ Tkinter๋กœ ๊ฐ„๋‹จํ•œ ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ ๋งŒ๋“ค๊ธฐ โ€“ FileMate Explorer

๐Ÿ“‚ FileMate Explorer โ€“ ๊ฐ€๋ฒผ์šด ํŒŒ์ด์ฌ ํŒŒ์ผ ๊ด€๋ฆฌ์ž. ์™„์ „ํžˆ ํŒŒ์ด์ฌ์œผ๋กœ ๋งŒ๋“  ๊ฐ€๋ฒผ์šด ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ๋ฅผ ์›ํ•˜์…จ๋‚˜์š”? Tkinter ๊ธฐ๋ฐ˜ FileMate Explorer๋ฅผ ๋งŒ๋‚˜๋ณด์„ธ์š”.