본문 바로가기
언어/Python

Python 나침판(Compass) 만들기.

by darkdevilness 2024. 1. 17.
728x90

나침판 Compass 를 만들어 보자 

통상 나침판이라 하면,   위에서 바라보는 N/S 극이  표시되는 동그란 판을 많이 볼 것이다. 

이번에 만든 것은 항법 장비에서 응용되는 나침판을 제작하였다. 

QT5 Slider로 만들면 되는데.  몇가지 제약 사항이 많아.  Graphic View and Scene을 이용하였다. 

삼각형 표시가 heading을 표시하는 것이고   heading은 중앙에서 움직이지 않는다. 

방향을 바꾸면.  scale이 좌 또는 우방향으로 이동하며,  Label이 바뀐다. 

바라보는 각은 120도 이며 프로그램 내부에서  rangeValue값을 변경하면 된다. 

아래 코드를 실행하면  아래와 같이  실행되며,  입력 spin 값은 Test를 위해 넣어 둔것이다. 

응용을 할때 입력 spin 항목은 제거 하고 사용하면 된다. 

항법 장비에서 응용되는 나침판

 

일반 나침판

 

Code

import math

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtGui import QPen, QFont
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from PyQt5.QtCore import Qt


class CCompassBar(QGraphicsView):

    def __init__(self, *args):
        self.scene = QGraphicsScene()
        super().__init__(self.scene)
        self.setScene(self.scene)
        self.setSceneRect(0, 0, 1000, 100)
        self.scene.setBackgroundBrush(Qt.white)
        self.scene.clear()

        # QGraphicsView setting
        self.setFixedHeight(60)
        self.setMinimumWidth(100)

        self.updateTicks(80)

    def resizeEvent(self, e):
        sceneRect = self.scene.sceneRect()
        self.fitInView(sceneRect)

        return

    def updateTicks(self, angle: int):
        angle %= 360
        ratio = 0.6

        sceneWidth, sceneHeight = 1000, 100
        self.scene.clear()
        self.scene.addRect(0, 0, sceneWidth, sceneHeight, QPen(Qt.white, 1))

        rangeValue = 120
        intervalValue = sceneWidth // rangeValue
        centerPoint = sceneWidth // 2
        intervalStep = 5 if rangeValue < 100 else 10

        #### Center Tick 을 먼저 그린 후에 좌우 눈금을 그린다.
        if angle % intervalStep:
            self.scene.addLine(centerPoint, 40, centerPoint, 60, QPen(Qt.red, 1))
        else:
            self.scene.addLine(centerPoint, 30, centerPoint, 70, QPen(Qt.blue, 1))
            info = self.scene.addText(str(angle))
            outRect = info.boundingRect()
            info.setPos(QPointF(centerPoint - outRect.width(), 80 - outRect.height() / 3))
            info.setFont(QFont(info.font().lastResortFont(), 20))
            # info.setDefaultTextColor(Qt.white)
            # info.hide()

        ### 중심을 기준으로 좌/우측을 동시에 그린다.
        for i in range(1, rangeValue // 2):
            x_loc = i * intervalValue

            anglePoint = angle + i
            if anglePoint % intervalStep:  # 우측 눈금을 그린다.
                self.scene.addLine(centerPoint + x_loc, 40, centerPoint + x_loc, 60, QPen(Qt.red, 1))
            else:
                self.scene.addLine(centerPoint + x_loc, 30, centerPoint + x_loc, 70, QPen(Qt.blue, 1))
                info = self.scene.addText(str(anglePoint % 360))
                outRect = info.boundingRect()
                info.setPos(QPointF(centerPoint + x_loc - outRect.width(), 80 - outRect.height() / 3))
                info.setFont(QFont(info.font().lastResortFont(), 20))
                # info.setDefaultTextColor(Qt.white)
                # info.hide()

                if anglePoint % 360 == 0:
                    txt = "N"
                elif anglePoint % 360 == 90:
                    txt = "E"
                elif anglePoint % 360 == 180:
                    txt = "S"
                elif anglePoint % 360 == 270:
                    txt = "W"
                else:
                    txt = ""
                if txt:
                    info = self.scene.addText(txt)
                    outRect = info.boundingRect()
                    info.setPos(QPointF(centerPoint + x_loc - outRect.width() / 1.2, 0))
                    info.setFont(QFont(info.font().lastResortFont(), 20))

            anglePoint = angle - i
            if anglePoint % intervalStep:  # 좌측 눈금을 그린다.
                self.scene.addLine(centerPoint - x_loc, 40, centerPoint - x_loc, 60, QPen(Qt.red, 1))
            else:
                self.scene.addLine(centerPoint - x_loc, 30, centerPoint - x_loc, 70, QPen(Qt.blue, 1))
                info = self.scene.addText(str(anglePoint % 360))
                outRect = info.boundingRect()
                info.setPos(QPointF(centerPoint - x_loc - outRect.width(), 80 - outRect.height() / 3))
                info.setFont(QFont(info.font().lastResortFont(), 20))
                # info.setDefaultTextColor(Qt.white)
                # info.hide()

                if anglePoint % 360 == 0:
                    txt = "N"
                elif anglePoint % 360 == 90:
                    txt = "E"
                elif anglePoint % 360 == 180:
                    txt = "S"
                elif anglePoint % 360 == 270:
                    txt = "W"
                else:
                    txt = ""
                if txt:
                    info = self.scene.addText(txt)
                    outRect = info.boundingRect()
                    info.setPos(QPointF(centerPoint - x_loc - outRect.width() / 1.2, 0))
                    info.setFont(QFont(info.font().lastResortFont(), 20))

        # Draw Triangle on Center Point
        bottomLength = 20
        bottomCenterPoint = sceneWidth // 2
        tri_height = int(bottomLength * math.tan(math.radians(60)) * 0.75)
        triangle_polygon = QPolygonF()
        triangle_polygon << QPointF(bottomCenterPoint - bottomLength, 0) \
                         << QPointF(bottomCenterPoint, tri_height) \
                         << QPointF(bottomCenterPoint + bottomLength, 0)
        self.scene.addPolygon(triangle_polygon, QPen(Qt.red, 1), QBrush(Qt.red))


class CMain(QWidget):
    signal = pyqtSignal(object, name="pyqtSignal")

    def __init__(self, *args):
        super().__init__()
        self.spinBox = QSpinBox(self)
        self.m_compassBar = CCompassBar()
        self.setupUI()

    def setSliceValue(self):
        val = self.spinBox.value()
        self.m_compassBar.updateTicks(val)

    def setupUI(self):
        self.spinBox.setRange(-720, 720)
        self.spinBox.valueChanged.connect(self.setSliceValue)

        centralLayout = QVBoxLayout()
        centralLayout.addWidget(self.m_compassBar)
        centralLayout.addWidget(self.spinBox)
        self.setLayout(centralLayout)


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    window = CMain()
    window.show()
    sys.exit(app.exec_())

 

나중에 아래와 같이 PrimaryFlightDisplay 를  만들어 볼 계획이다. 

728x90