๐งฑ QRForge PRO๋ฅผ PySide6์ผ๋ก ๋น๋ํ๊ธฐ (์ด๋ณด์ ์นํ ๊ฐ์ด๋)
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์ ๊ฐ๋ฅด์น ๋
