PyQt Protect/SmartFac: PyQt를 이용한 스마트 팩토리 인터페이스 개발

PyQt를 사용하여 원형 진행바(Round ProgressBar) 만들기 (Green Type)

Pink_Bear 2023. 6. 27. 16:12

이걸 만들어봅시다.

2023.06.21 - [PyQt Protect/SmartFac: PyQt를 이용한 스마트 팩토리 인터페이스 개발] - PyQt를 사용하여 원형 진행바(Round ProgressBar) 만들기

 

PyQt를 사용하여 원형 진행바(Round ProgressBar) 만들기

Goal : 원형 진행바(Round ProgressBar) 위젯을 만들어봅시다. Purpose : Smart Factory에서 자동화 프로세스가 어느정도 진행되었는지 알려줄 목적으로 개발합니다. Round ProcessBar는 시간의 흐름을 시각적으로

heedaelcobox.tistory.com

Goal : 원형 진행바(Round ProgressBar) 위젯을 만들어봅시다.

 

Purpose :

  • 지난 글과 동일합니다.

Code Devleopment Process :

 

1) 간단한 Window 위젯을 만들어줍시다.

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Round ProcessBar')
        self.setGeometry(0, 0, 200, 200)
        
if __name__ == '__main__':
   app = QApplication(sys.argv)
   ex = Window()
   ex.show()
   sys.exit(app.exec_())

아직 아무것도 없어요.

2) Painter 이벤트를 사용해서 뒤에 배경을 채워봅시다.

import sys
from PyQt5 import QtGui
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Round ProcessBar')
        self.setGeometry(300, 300, 200, 200)
        
    def paintEvent(self, a0: QPaintEvent) -> None:
        painter = QPainter(self)
        # # 1. 배경 색칠하기
        painter.setBrush(QBrush(QColor(255, 255, 255, 255), Qt.SolidPattern))
        painter.drawRect(self.rect())
        painter.end()
                        
if __name__ == '__main__':
   app = QApplication(sys.argv)
   ex = Window()
   ex.show()
   sys.exit(app.exec_())

 

흰색으로 배경을 채웠습니다.

3) Painter 이벤트와 Arc를 사용하여 반원을 그려줍니다.

  • Arc를 그릴 때 계산에 늘 어렵습니다. 아래 그림을 참고해주세요. 먼저 시작지점을 나타내는 파란색 기준점을 찾습니다. 파란색을 기준으로 왼쪽 오른쪽으로 움직이는 것이 빨간색 끝나는 지점입니다.
  • 아래 그림을 예시로 설명드리겠습니다. 현재 우리가 구현하고자하는 목표에 비추어 보았을때 흰도화지와 회색 붓이 있다고 가정합시다. drawArc의 첫번째 인자는 도화지의 크기를 의미합니다. 두번째 인자는 시작점이며, default 값이 0일때 xy평면상 오른쪽에 위치합니다. (각도가 0입니다.) 값이 증가할 수 록 시계반대방향으로 흘러값니다. 아래 파란색 실선 화살표를 참고하시면, 이해할 수 있습니다. 세번째 인자는 끝나는 점입니다. 회색 붓을 들고 시작점부터 시계반대방향으로 그리는 것입니다.
  • 우리는 시계방향(왼쪽에서 오른쪽)으로 증가하는 Indicator를 설계할 것이므로 음(-16씩)의 방향으로 이동시키도록 만들면됩니다.

 

늘 어렵습니다.

    def paintEvent(self, a0: QPaintEvent) -> None:
        painter = QPainter(self)
        # 1. 배경 색칠하기
        painter.setBrush(QBrush(QColor(255, 255, 255, 255), Qt.SolidPattern))
        painter.drawRect(self.rect())
        # 2. Progress 원 배경 그리기
        start_angle =   225 * 16 # [0 ~ 5760]
        end_angle   = - 270 * 16 
        tkEllipseLine = 25 # 원 두께
        new_rect = QRect(tkEllipseLine, tkEllipseLine, self.rect().width() - tkEllipseLine*2, self.rect().height() - tkEllipseLine*2)
        
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(238, 239, 240, 255), tkEllipseLine))
        painter.drawArc(new_rect, start_angle, end_angle)
        
        painter.end()

말발굽 모양처럼 생긴 그림이 생성되었습니다.

4) Painter 이벤트에서 Arc를 사용하여 내부를 채워줍시다.

    def paintEvent(self, a0: QPaintEvent) -> None:
        painter = QPainter(self)
        # 1. 배경 색칠하기
        painter.setBrush(QBrush(QColor(255, 255, 255, 255), Qt.SolidPattern))
        painter.drawRect(self.rect())
        # 2. Progress 원 배경 그리기
        start_angle = 225 * 16 # [0 ~ 5760]
        tkEllipseLine = 25 # 원 두께
        new_rect = QRect(tkEllipseLine, tkEllipseLine, self.rect().width() - tkEllipseLine*2, self.rect().height() - tkEllipseLine*2)

        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(238, 239, 240, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - 270 * 16)
        # 3. Progress 내부 원 그리기
        painter.setPen(QPen(QColor(131, 217, 212, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - self.value * 2.7 * 16)
        
        painter.end()
  • 앞선 Arc를 그리는 것과 동일합니다. self.value의 범위가 0~100 사이인 것을 고려하여 -self.value * 2.7 * 16으로 넣어줍시다. 추가적으로, 우리가 목표하는 그래프는 양 끝단이 둥근모양을 가지고 있어서 이를 반영해줍시다.
    • # 수정된 라인
      # 2. Progress 원
      painter.setPen(QPen(QColor(238, 239, 240, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
      # 3. Progress 내부 원
      painter.setPen(QPen(QColor(131, 217, 212, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
      painter.drawArc(new_rect, 225 * 16, - self.value * 2.7 * 16)
  • PyQt 웹에서 다른모양도 제공합니다.

PyQt Cap Style [https://doc.qt.io/qtforpython-5/PySide2/QtGui/QPen.html]
이제 점점 나가지고 있습니다. 그런데 점을 빼먹었네요.

 

4) 내부 Arc에 흰점을 그리고 텍스트도 넣어줍시다.

    def paintEvent(self, a0: QPaintEvent) -> None:
        painter = QPainter(self)
        # 1. 배경 색칠하기
        painter.setBrush(QBrush(QColor(255, 255, 255, 255), Qt.SolidPattern))
        painter.drawRect(self.rect())
        # 2. Progress 원 배경 그리기
        start_angle = 225 * 16 # [0 ~ 5760]
        tkEllipseLine = 25 # 원 두께
        new_rect = QRect(tkEllipseLine, tkEllipseLine, self.rect().width() - tkEllipseLine*2, self.rect().height() - tkEllipseLine*2)

        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(238, 239, 240, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - 270 * 16)
        # 3. Progress 내부 원 그리기
        painter.setPen(QPen(QColor(131, 217, 212, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - self.value * 2.7 * 16)
        # 3.1 Progress 내부 원에 점 그리기
        painter.setPen(QPen(QColor(255, 255, 255, 255), 15, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, (225 - self.value * 2.7) * 16, 1)
        # 4. Value 값 text 표시하기
        painter.setPen(QPen(QColor(117, 143, 143, 255)))
        painter.setFont(QFont("Arial", 25, QFont.Bold, False))
        painter.drawText(new_rect, Qt.AlignmentFlag.AlignCenter, f'{self.value}')
        painter.end()
  • drawEllipse를 사용하여 원을 그리는 것도 하나의 방법이지만, 나중에 위치 계산이 어렵습니다. 하나의 아이디어는 우리는 내부 Progress 원에 끝나는 점을 알고있습니다. 때문에, 이 지점을 시작점으로 하여 Arc를 끝점에 1 정도를 넣어 원을 그린것과 같은 효과를 낼 수 있습니다.

완성입니다. 그런데 뭐가좀 부족하네요...

5) 그래프 타이틀 넣고 원위치 조정하기.

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Round ProcessBar')
        self.setGeometry(300, 300, 200, 200)
        self.value = 10
        self.title = "PinkBear★"
        
    def paintEvent(self, a0: QPaintEvent) -> None:
        painter = QPainter(self)
        # 0. 위치 조정
        move_y = 25
        # 1. 배경 색칠하기
        painter.setBrush(QBrush(QColor(255, 255, 255, 255), Qt.SolidPattern))
        painter.drawRect(self.rect())
        # 2. Progress 원 배경 그리기
        start_angle = 225 * 16 # [0 ~ 5760]
        tkEllipseLine = 25 # 원 두께
        new_rect = QRect(tkEllipseLine, tkEllipseLine + move_y, self.rect().width() - tkEllipseLine*2, self.rect().height() - tkEllipseLine*2)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(238, 239, 240, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - 270 * 16)
        # 3. Progress 내부 원 그리기
        painter.setPen(QPen(QColor(131, 217, 212, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - self.value * 2.7 * 16)
        # 3.1 Progress 내부 원에 점 그리기
        painter.setPen(QPen(QColor(255, 255, 255, 255), 15, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, (225 - self.value * 2.7) * 16, 1)
        # 4. Value 값 text 표시하기
        painter.setPen(QPen(QColor(117, 143, 143, 255)))
        painter.setFont(QFont("Arial", 25, QFont.Bold, False))
        painter.drawText(new_rect, Qt.AlignmentFlag.AlignCenter, f'{self.value}')
        # 5. 상단 title 표시하기
        painter.setFont(QFont("Arial", 15, QFont.Bold, False))
        painter.drawText(self.rect().adjusted(5, 5, 5, 5), Qt.AlignmentFlag.AlignTop, self.title)
        painter.end()
  • Round Indicator의 위치를 조정하도록 하겠습니다. self.paintEvent에서 move_y를 추가하고 new_rect에 y축 위치를 조정합니다.
    • move_y = 25
      ...
      new_rect = QRect(tkEllipseLine, tkEllipseLine + move_y, self.rect().width() - tkEllipseLine*2, self.rect().height() - tkEllipseLine*2)
  • 또한, Title 텍스트를 입력하기 위해서 self.init에 Title 이 입력될 변수를 self.title로 만들어 주었습니다. 그리고 self.paintEvent에 2줄을 추가해줍시다. 텍스트 Title이 너무 왼쪽상단에 붙는것을 방지하기 위해서 5 정도 여백을 주었습니다. (self.rect() 전체를 5씩 대각선 방향으로 이동시켜 여백이 발생한것과 같은 효과를 주었습니다.)
    • # 5. 상단 title 표시하기
      painter.setFont(QFont("Arial", 15, QFont.Bold, False))
      painter.drawText(self.rect().adjusted(5, 5, 5, 5), Qt.AlignmentFlag.AlignTop, self.title)

완성입니다!.


import sys
from PyQt5 import QtGui
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Round ProcessBar')
        self.setGeometry(300, 300, 200, 200)
        self.value = 0
        self.title = "PinkBear★"
        
    def paintEvent(self, a0: QPaintEvent) -> None:
        painter = QPainter(self)
        # 0. 위치 조정
        move_y = 25
        # 1. 배경 색칠하기
        painter.setBrush(QBrush(QColor(255, 255, 255, 255), Qt.SolidPattern))
        painter.drawRect(self.rect())
        # 2. Progress 원 배경 그리기
        start_angle = 225 * 16 # [0 ~ 5760]
        tkEllipseLine = 25 # 원 두께
        new_rect = QRect(tkEllipseLine, tkEllipseLine + move_y, self.rect().width() - tkEllipseLine*2, self.rect().height() - tkEllipseLine*2)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(QColor(238, 239, 240, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - 270 * 16)
        # 3. Progress 내부 원 그리기
        painter.setPen(QPen(QColor(131, 217, 212, 255), tkEllipseLine, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, 225 * 16, - self.value * 2.7 * 16)
        # 3.1 Progress 내부 원에 점 그리기
        painter.setPen(QPen(QColor(255, 255, 255, 255), 15, cap=Qt.PenCapStyle.RoundCap))
        painter.drawArc(new_rect, (225 - self.value * 2.7) * 16, 1)
        # 4. Value 값 text 표시하기
        painter.setPen(QPen(QColor(117, 143, 143, 255)))
        painter.setFont(QFont("Arial", 25, QFont.Bold, False))
        painter.drawText(new_rect, Qt.AlignmentFlag.AlignCenter, f'{self.value}')
        # 5. 상단 title 표시하기
        painter.setFont(QFont("Arial", 15, QFont.Bold, False))
        painter.drawText(self.rect().adjusted(5, 5, 5, 5), Qt.AlignmentFlag.AlignTop, self.title)
        painter.end()
        
    def keyPressEvent(self, a0: QKeyEvent) -> None:
        if a0.key() == Qt.Key.Key_Up:
            self.value = self.value + 1 if 0 <= self.value < 100 else self.value
        if a0.key() == Qt.Key.Key_Down:
            self.value = self.value - 1 if 0 < self.value <= 100 else self.value
        self.update()
                        
if __name__ == '__main__':
   app = QApplication(sys.argv)
   ex = Window()
   ex.show()
   sys.exit(app.exec_())