PythonOCC进阶学习:界面设计——重新打造主界面

总目录 >> PythonOCC入门进阶到实战(目前已更新入门篇、基础篇和进阶篇)


写在前面

刚开始的时候,只是比较多的关注功能层的实现,但是随着项目的推进,慢慢发现现有官方提供的主界面过于简单,再加上一些cad开发者也在不断地询问哪里有界面开发的教程,我想稍微总结一下之前做界面的经历,希望能够给大家一些启发和帮助。如果你觉得有用,关注下我,这就是对我最大的支持。

1.开发前提

首先要确认你已经安装了pyqt5,没有安装的,自行安装pip install pyqt5
关于使用pyside,wxpython的朋友,这方面我没太研究,但是本质应该差别不大。

本教程只是对主界面进行一个初步开发,如果你想拥有比较完善美观的界面,可以参考下这本书:pyqt5快速开发与实践

2.依赖文件 qtDisplay. py

将如下代码复制到名为qtDisplay. py,放到和主程序mainwindow(下面讲)同一文件夹

import logging
import os
import sys

from OCC.Display import OCCViewer

from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL
# check if signal available, not available
# on PySide
HAVE_PYQT_SIGNAL = hasattr(QtCore, 'pyqtSignal')

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
log = logging.getLogger(__name__)


class point(object):
    def __init__(self, obj=None):
        self.x = 0
        self.y = 0
        if obj is not None:
            self.set(obj)

    def set(self, obj):
        self.x = obj.x()
        self.y = obj.y()


class qtBaseViewer(QtOpenGL.QGLWidget):
    ''' The base Qt Widget for an OCC viewer
    '''

    def __init__(self, parent=None):
        QtOpenGL.QGLWidget.__init__(self, parent)
        self._display = None
        self._inited = False

        # enable Mouse Tracking
        self.setMouseTracking(True)
        # Strong focus
        self.setFocusPolicy(QtCore.Qt.WheelFocus)

        # required for overpainting the widget
        self.setAttribute(QtCore.Qt.WA_PaintOnScreen)
        self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
        self.setAutoFillBackground(False)

    def GetHandle(self):
        ''' returns an the identifier of the GUI widget.
        It must be an integer
        '''
        win_id = self.winId()  # this returns either an int or voitptr

        if "%s" % type(win_id) == "<type 'PyCObject'>":  # PySide
            ### with PySide, self.winId() does not return an integer
            if sys.platform == "win32":
                ## Be careful, this hack is py27 specific
                ## does not work with python31 or higher
                ## since the PyCObject api was changed
                import ctypes
                ctypes.pythonapi.PyCObject_AsVoidPtr.restype = ctypes.c_void_p
                ctypes.pythonapi.PyCObject_AsVoidPtr.argtypes = [
                    ctypes.py_object]
                win_id = ctypes.pythonapi.PyCObject_AsVoidPtr(win_id)
        elif not isinstance(win_id, int):  # PyQt4 or 5
            ## below integer cast may be required because self.winId() can
            ## returns a sip.voitptr according to the PyQt version used
            ## as well as the python version
            win_id = int(win_id)
        return win_id

    def resizeEvent(self, event):
        if self._inited:
            super(qtBaseViewer,self).resizeEvent(event)
            self._display.OnResize()


class qtViewer3d(qtBaseViewer):

    # emit signal when selection is changed
    # is a list of TopoDS_*
    if HAVE_PYQT_SIGNAL:
        sig_topods_selected = QtCore.pyqtSignal(list)

    def __init__(self, *kargs):
        qtBaseViewer.__init__(self, *kargs)

        self.setObjectName("qt_viewer_3d")

        self._drawbox = False
        self._zoom_area = False
        self._select_area = False
        self._inited = False
        self._leftisdown = False
        self._middleisdown = False
        self._rightisdown = False
        self._selection = None
        self._drawtext = True
        self._qApp = QtWidgets.QApplication.instance() 
        self._key_map = {}
        self._current_cursor = "arrow"
        self._available_cursors = {}

    @property
    def qApp(self):
        # reference to QApplication instance
        return self._qApp

    @qApp.setter
    def qApp(self, value):
        self._qApp = value

    def InitDriver(self):
        self._display = OCCViewer.Viewer3d(self.GetHandle())
        self._display.Create()
        # background gradient
        self._display.set_bg_gradient_color(206, 215, 222, 128, 128, 128)
        # background gradient
        self._display.display_trihedron()
        self._display.SetModeShaded()
        self._display.DisableAntiAliasing()
        self._inited = True
        # dict mapping keys to functions
        self._SetupKeyMap()
        #
        self._display.thisown = False
        self.createCursors()

    def createCursors(self):
        module_pth = os.path.abspath(os.path.dirname(__file__))
        icon_pth = os.path.join(module_pth, "icons")

        _CURSOR_PIX_ROT = QtGui.QPixmap(os.path.join(icon_pth, "cursor-rotate.png"))
        _CURSOR_PIX_PAN = QtGui.QPixmap(os.path.join(icon_pth, "cursor-pan.png"))
        _CURSOR_PIX_ZOOM = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify.png"))
        _CURSOR_PIX_ZOOM_AREA = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify-area.png"))

        self._available_cursors = {
            "arrow": QtGui.QCursor(QtCore.Qt.ArrowCursor),  # default
            "pan": QtGui.QCursor(_CURSOR_PIX_PAN),
            "rotate": QtGui.QCursor(_CURSOR_PIX_ROT),
            "zoom": QtGui.QCursor(_CURSOR_PIX_ZOOM),
            "zoom-area": QtGui.QCursor(_CURSOR_PIX_ZOOM_AREA),
        }

        self._current_cursor = "arrow"

    def _SetupKeyMap(self):
        self._key_map = {ord('W'): self._display.SetModeWireFrame,
                         ord('S'): self._display.SetModeShaded,
                         ord('A'): self._display.EnableAntiAliasing,
                         ord('B'): self._display.DisableAntiAliasing,
                         ord('H'): self._display.SetModeHLR,
                         ord('F'): self._display.FitAll,
                         ord('G'): self._display.SetSelectionMode}

    def keyPressEvent(self, event):
        code = event.key()
        if code in self._key_map:
            self._key_map[code]()
        else:
            log.info("key: %s \\nnot mapped to any function", code)

    def Test(self):
        if self._inited:
            self._display.Test()

    def focusInEvent(self, event):
        if self._inited:
            self._display.Repaint()

    def focusOutEvent(self, event):
        if self._inited:
            self._display.Repaint()

    def paintEvent(self, event):
        if self._inited:
            self._display.Context.UpdateCurrentViewer()
            # important to allow overpainting of the OCC OpenGL context in Qt
            self.swapBuffers()

        if self._drawbox:
            self.makeCurrent()
            painter = QtGui.QPainter(self)
            painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1))
            rect = QtCore.QRect(*self._drawbox)
            painter.drawRect(rect)
            painter.end()
            self.doneCurrent()

    def ZoomAll(self, evt):
        self._display.FitAll()

    def wheelEvent(self, event):
        try:  # PyQt4/PySide
            delta = event.delta()
        except:  # PyQt5
            delta = event.angleDelta().y()
        if delta > 0:
            zoom_factor = 2.
        else:
            zoom_factor = 0.5
        self._display.Repaint()
        self._display.ZoomFactor(zoom_factor)

    def dragMoveEvent(self, event):
        pass

    @property
    def cursor(self):
        return self._current_cursor

    @cursor.setter
    def cursor(self, value):
        if not self._current_cursor == value:

            self._current_cursor = value
            cursor = self._available_cursors.get(value)

            if cursor:
                self.qApp.setOverrideCursor(cursor)
            else:
                self.qApp.restoreOverrideCursor()

    def mousePressEvent(self, event):
        self.setFocus()
        self.dragStartPos = point(event.pos())
        self._display.StartRotation(self.dragStartPos.x, self.dragStartPos.y)

    def mouseReleaseEvent(self, event):
        pt = point(event.pos())
        modifiers = event.modifiers()

        if event.button() == QtCore.Qt.LeftButton:
            if self._select_area:
                [Xmin, Ymin, dx, dy] = self._drawbox
                self._display.SelectArea(Xmin, Ymin, Xmin + dx, Ymin + dy)
                self._select_area = False
            else:
                # multiple select if shift is pressed
                if modifiers == QtCore.Qt.ShiftModifier:
                    self._display.ShiftSelect(pt.x, pt.y)
                else:
                    # single select otherwise
                    self._display.Select(pt.x, pt.y)

                    if (self._display.selected_shapes is not None) and HAVE_PYQT_SIGNAL:
                        self.sig_topods_selected.emit(self._display.selected_shapes)


        elif event.button() == QtCore.Qt.RightButton:
            if self._zoom_area:
                [Xmin, Ymin, dx, dy] = self._drawbox
                self._display.ZoomArea(Xmin, Ymin, Xmin + dx, Ymin + dy)
                self._zoom_area = False

        self.cursor = "arrow"

    def DrawBox(self, event):
        tolerance = 2
        pt = point(event.pos())
        dx = pt.x - self.dragStartPos.x
        dy = pt.y - self.dragStartPos.y
        if abs(dx) <= tolerance and abs(dy) <= tolerance:
            return
        self._drawbox = [self.dragStartPos.x, self.dragStartPos.y, dx, dy]
        self.update()

    def mouseMoveEvent(self, evt):
        pt = point(evt.pos())
        buttons = int(evt.buttons())
        modifiers = evt.modifiers()
        # ROTATE
        if (buttons == QtCore.Qt.LeftButton and
                not modifiers == QtCore.Qt.ShiftModifier):
            dx = pt.x - self.dragStartPos.x
            dy = pt.y - self.dragStartPos.y
            self.cursor = "rotate"
            self._display.Rotation(pt.x, pt.y)
            self._drawbox = False
        # DYNAMIC ZOOM
        elif (buttons == QtCore.Qt.RightButton and
              not modifiers == QtCore.Qt.ShiftModifier):
            self.cursor = "zoom"
            self._display.Repaint()
            self._display.DynamicZoom(abs(self.dragStartPos.x),
                                      abs(self.dragStartPos.y), abs(pt.x),
                                      abs(pt.y))
            self.dragStartPos.x = pt.x
            self.dragStartPos.y = pt.y
            self._drawbox = False
        # PAN
        elif buttons == QtCore.Qt.MidButton:
            dx = pt.x - self.dragStartPos.x
            dy = pt.y - self.dragStartPos.y
            self.dragStartPos.x = pt.x
            self.dragStartPos.y = pt.y
            self.cursor = "pan"
            self._display.Pan(dx, -dy)
            self._drawbox = False
        # DRAW BOX
        # ZOOM WINDOW
        elif (buttons == QtCore.Qt.RightButton and
              modifiers == QtCore.Qt.ShiftModifier):
            self._zoom_area = True
            self.cursor = "zoom-area"
            self.DrawBox(evt)
        # SELECT AREA
        elif (buttons == QtCore.Qt.LeftButton and
              modifiers == QtCore.Qt.ShiftModifier):
            self._select_area = True
            self.DrawBox(evt)
        else:
            self._drawbox = False
            self._display.MoveTo(pt.x, pt.y)
            self.cursor = "arrow"

3.主文件 mainwindow. py

# -*- coding: UTF-8 -*-
import sys
from OCC.gp import gp_Pnt
from OCC.Extend.DataExchange import read_iges_file
from PyQt5.QtGui import QIcon

from qtDisplay import qtViewer3d
from PyQt5 import  QtCore
from PyQt5.QtWidgets import QFileDialog, QAction, QApplication, QMenu, QMainWindow, QHBoxLayout, QDockWidget, \\
    QListWidget, QLabel, qApp





class MainWindow(QMainWindow):
    def __init__(self, *args):
        ''' 初始化界面 '''
        # 中心控件
        # 中心控件
        QMainWindow.__init__(self, *args)
        layout=QHBoxLayout()         # 水平布局
        self.canva = qtViewer3d(self)
        # 显示设置
        self.canva.InitDriver()        # canva的驱动,设置驱动后,才能成功display
        display = self.canva._display
        display.set_bg_gradient_color(206, 215, 222, 128, 128, 128)  # 设置背景渐变色
        display.display_trihedron()   # display black trihedron
        self.setWindowTitle("Aim")
        # self.setWindowIcon(QIcon('./icons/aim.png'))        #设置程序图标
        self.setCentralWidget(self.canva)
        self.resize(1024,768)
        self.centerOnScreen()

        # 容器控件
        # 容器控件
        self.items=QDockWidget('功能区',self)
        self.listWidget=QListWidget()
        self.listWidget.addItem('item1')
        self.listWidget.addItem('item2')
        self.listWidget.addItem('item3')
        self.items.setWidget(self.listWidget)
        self.items.setFloating(False)
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea,self.items)


        # #### 菜单栏
        # #### 菜单栏

        menubar = self.menuBar()
        # 第一个菜单栏‘打开文件’
        file=menubar.addMenu('&打开文件')
        file.addAction('模型导入')
        file.addAction('打开表格文件')
        file.addAction('打开图片')

        # #### 工具栏

        #工具一:打开文件
        exitAction = QAction(QIcon('./icons/open.gif'), '打开文件', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.triggered.connect(QFileDialog.getOpenFileName)
        self.toolbar = self.addToolBar('Exit')
        self.toolbar.addAction(exitAction)

        # #### label标签
        label=QLabel(self)
        label.move(900,0)
        label.setText("<a href='https://blog.csdn.net/weixin_42755384/article/details/87893697'>PythonOCC帮助</a>")
        label.setOpenExternalLinks(True)
        self.setLayout(layout)
        # 临时读入一个文件
        P0 = gp_Pnt(0, 0, 1)
        P1 = gp_Pnt(0, 30, 20)
        display.DisplayShape(P0)
        display.DisplayShape(P1)
        # display.DisplayShape(shapes,update=True)
    def centerOnScreen(self):
        '''屏幕居中'''
        resolution = QApplication.desktop().screenGeometry()
        x = (resolution.width() - self.frameSize().width()) / 2
        y = (resolution.height() - self.frameSize().height()) / 2
        self.move(x, y)



if __name__ == '__main__':
        app = QApplication(sys.argv)  # 创建应用
        win = MainWindow()            # 创建主窗口
        win.canva.qApp = app          # 将自己创建的应用与canva中的 Qapp连接起来
        win.show()                    # 主窗口显示
        win.canva.InitDriver()        # 社区反馈,添加此语句,以下的bug不会出现。
        win.raise_()                  # 窗口置顶
        sys.exit(app.exec_())

如果没什么意外的话,会出现如下界面:
在这里插入图片描述
这里是有一个小bug的,就是显示区是黑的,但是然后你全屏一下就恢复了,后面我再修复这个bug。
理想版本如下
在这里插入图片描述

© 版权声明
THE END
喜欢就支持一下吧
点赞462 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容